From 1155f775b435177d0c2c39d58a4b4e6f403a0a9b Mon Sep 17 00:00:00 2001 From: Finn Roberts Date: Wed, 25 Sep 2024 09:22:45 -0400 Subject: [PATCH 01/35] Update .gitignore --- .gitignore | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.gitignore b/.gitignore index ba39bd2..3e742ae 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ __pycache__ .venv .pytest_cache dist +.python-version # Editor cruft .vscode @@ -12,6 +13,7 @@ dist # Secrets .env.test +.env # Docs docs/_build/ @@ -25,3 +27,6 @@ coverage.xml # pycharm ide settings .idea/ + +# Ignore .DS_Store files +.DS_Store From d5169a49c14dea9d6544e2379786866ab6e0b8d5 Mon Sep 17 00:00:00 2001 From: Finn Roberts Date: Thu, 26 Sep 2024 16:03:21 -0400 Subject: [PATCH 02/35] Add AggregateDataExtract class Co-authored-by: Ben Levesque --- src/ipumspy/api/extract.py | 187 +++++++++++++++++++++++++++++++++++++ 1 file changed, 187 insertions(+) diff --git a/src/ipumspy/api/extract.py b/src/ipumspy/api/extract.py index 3933e87..7c22da3 100644 --- a/src/ipumspy/api/extract.py +++ b/src/ipumspy/api/extract.py @@ -125,6 +125,71 @@ def build(self): return built_tuv +@dataclass +class Dataset(IpumsObject): + """ + IPUMS Dataset object to include in an AggregateDataExtract object. + """ + + name: str + """IPUMS NHGIS dataset name/id""" + data_tables: List[str] + """IPUMS NHGIS data tables to extract from this dataset""" + geog_levels: List[str] + """Geographic level(s) at which to obtain data for this dataset""" + years: Optional[List[str]] = field(default_factory=list) + """Years for which to obtain data for this dataset""" + breakdown_values: Optional[List[str]] = field(default_factory=list) + """Breakdown values to apply to this dataset""" + + def build(self): + built_dataset = self.__dict__.copy() + # don't repeat the dataset name + built_dataset.pop("name") + # adhere to API schema camelCase convention + built_dataset["dataTables"] = built_dataset.pop("data_tables") + built_dataset["geogLevels"] = built_dataset.pop("geog_levels") + built_dataset["years"] = built_dataset.pop("years") + built_dataset["breakdownValues"] = built_dataset.pop("breakdown_values") + + return built_dataset + +@dataclass +class TimeSeriesTable(IpumsObject): + """ + IPUMS TimeSeriesTable object to include in an AggregateDataExtract object. + """ + + name: str + """IPUMS NHGIS time series table name/id""" + geog_levels: List[str] # required parameter + """Geographic level(s) at which to obtain data for this time series table""" + years: Optional[List[str]] = field(default_factory=list) + """Years for which to obtain data for this time series table""" + + def build(self): + built_tst = self.__dict__.copy() + # don't repeat the time series table name + built_tst.pop("name") + # adhere to API schema camelCase convention + built_tst["geogLevels"] = built_tst.pop("geog_levels") + built_tst["years"] = built_tst.pop("years") + + return built_tst + +@dataclass +class Shapefile(IpumsObject): + """ + IPUMS Shapefile object to include in an AggregateDataExtract object. + """ + + name: str + """IPUMS NHGIS shapefile name/id""" + + def build(self): + raise NotImplementedError + + def _unpack_samples_dict(dct: dict) -> List[Sample]: return [Sample(id=samp) for samp in dct.keys()] @@ -159,6 +224,30 @@ def _unpack_tuv_dict(dct: dict) -> List[TimeUseVariable]: return tuvs +def _unpack_dataset_dict(dct: dict) -> List[Dataset]: + datasets = [] + for dataset in dct.keys(): + dataset_obj = Dataset(name=dataset, data_tables=dct[dataset]["dataTables"], geog_levels=dct[dataset]["geogLevels"]) + if "years" in dct[dataset]: + dataset_obj.update("years", dct[dataset]["years"]) + if "breakdownValues" in dct[dataset]: + dataset_obj.update("breakdown_values", dct[dataset]["breakdownValues"]) + datasets.append(dataset_obj) + return datasets + +def _unpack_tst_dict(dct: dict) -> List[TimeSeriesTable]: + time_series_tables = [] + for time_series_table in dct.keys(): + time_series_table_obj = TimeSeriesTable(name=time_series_table, geog_levels=dct[time_series_table]["geogLevels"]) + if "years" in dct[time_series_table]: + time_series_table_obj.update("years", dct[time_series_table]["years"]) + time_series_tables.append(time_series_table_obj) + + return time_series_tables + +def _unpack_shapefiles_dict(dct: dict) -> List[Shapefile]: + return [Shapefile(name=shapefile) for shapefile in dct.keys()] + class BaseExtract: _collection_type_to_extract: Dict[(str, str), Type[BaseExtract]] = {} @@ -265,6 +354,16 @@ def _validate_list_args(self, list_arg, arg_obj): elif isinstance(list_arg, dict) and arg_obj is TimeUseVariable: args = _unpack_tuv_dict(list_arg) return args + elif isinstance(list_arg, dict) and arg_obj is Dataset: + args = _unpack_dataset_dict(list_arg) + return args + elif isinstance(list_arg, dict) and arg_obj is TimeSeriesTable: + args = _unpack_tst_dict(list_arg) + return(args) + elif isinstance(list_arg, dict) and arg_obj is Shapefile: + args = _unpack_shapefiles_dict(list_arg) + return(args) + # Make sure extracts don't get built with duplicate variables or samples # if the argument is a list of objects, make sure there are not objects with duplicate names elif all(isinstance(i, arg_obj) for i in list_arg): @@ -504,6 +603,94 @@ def select_cases( variable, "case_selections", {"detailed": values} ) +class AggregateDataExtract(BaseExtract, collection_type="aggregate_data"): + def __init__( + self, + collection: str, + datasets: Optional[Union[List[str], List[Dataset]]] = [], + timeSeriesTables: Optional[Union[List[str], List[TimeSeriesTable]]] = [], + shapefiles: Optional[Union[List[str], List[Shapefile]]] = [], + description: str = "", + data_format: str = "csv_no_header", + # geographic_extents: Optional[List[str]] = None, + tst_layout: str = "time_by_column_layout", + breakdown_and_data_type_layout: str = "single_file", + **kwargs + ): + """ + Class for defining an IPUMS NHGIS extract request. + + Args: + datasets: list of ``Dataset`` objects + time_series_tables: list of ``TimeSeriesTable`` objects + shapefiles: list of shapefile IDs from IPUMS NHGIS + description: short description of your extract + data_format: desired format of the extract data file. One of ``"csv_no_header"``, ``"csv_header"``, or ``"fixed_width"``. + breakdown_and_data_type_layout: desired layout of any `datasets` that have multiple data types or breakdown values. Either + ``"single_file"`` (default) or ``"separate files"`` + time_series_table_layout: desired data layout for all ``time_series_tables`` in the extract definition. + One of ``"time_by_column_layout"``, ``"time_by_row_layout"``, or ``"time_by_file_layout"``. + """ + + super().__init__() + + self.collection = collection + self.collection_type = self.collection_type + + self.datasets = self._validate_list_args(datasets, Dataset) + self.time_series_tables = self._validate_list_args(timeSeriesTables, TimeSeriesTable) + self.shapefiles = self._validate_list_args(shapefiles, Shapefile) + + if len(self.datasets) == 0 and len(self.time_series_tables) == 0 and len(self.shapefiles) == 0: + raise ValueError("At least one dataset, time series table, or shapefile must be specified.") + + self.description = description + self.data_format = data_format + self.breakdown_and_data_type_layout = breakdown_and_data_type_layout + self.time_series_table_layout = tst_layout + + self.api_version = ( + self.extract_api_version(kwargs) + if len(kwargs.keys()) > 0 + else self.api_version + ) + """IPUMS API version number""" + + # check kwargs for conflicts with defaults + self._kwarg_warning(kwargs) + # make the kwargs camelCase + self.kwargs = self._snake_to_camel(kwargs) + + def build(self) -> Dict[str, Any]: + """ + Convert the object into a dictionary to be passed to the IPUMS API + as a JSON string + """ + + built = { + "description": self.description, + "dataFormat": self.data_format, + "collection": self.collection, + "version": self.api_version, + **self.kwargs, + } + + if self.datasets is not None: + built["datasets"] = { + dataset.name: dataset.build() for dataset in self.datasets + } + built["breakdownAndDataTypeLayout"] = self.breakdown_and_data_type_layout + + if self.time_series_tables is not None: + built["timeSeriesTables"] = { + tst.name.upper(): tst.build() for tst in self.time_series_tables + } + built["timeSeriesTableLayout"] = self.time_series_table_layout + + if self.shapefiles is not None: + built["shapefiles"] = [shapefile.name for shapefile in self.shapefiles] + + return built def extract_from_dict(dct: Dict[str, Any]) -> Union[BaseExtract, List[BaseExtract]]: """ From 969d517cbb5fd490b8c6cab333dfd3447d006a5e Mon Sep 17 00:00:00 2001 From: Finn Roberts Date: Thu, 26 Sep 2024 17:30:35 -0400 Subject: [PATCH 03/35] Enable get_extract_by_id() for AggregateDataExtract objects --- src/ipumspy/api/core.py | 20 ++++++++++++++++++-- src/ipumspy/api/extract.py | 2 +- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/ipumspy/api/core.py b/src/ipumspy/api/core.py index c5e30ec..f3e19a4 100644 --- a/src/ipumspy/api/core.py +++ b/src/ipumspy/api/core.py @@ -64,6 +64,20 @@ def _extract_and_collection( ) return extract_id, collection +def _get_collection_type(collection: str) -> str: + collection_types = { + "usa": "microdata", + "cps": "microdata", + "ipumsi": "microdata", + "atus": "microdata", + "ahtus": "microdata", + "mtus": "microdata", + "nhis": "microdata", + "meps:": "microdata", + "nhgis": "aggregate_data" + } + + return collection_types[collection] def _prettify_message(response_message: Union[str, List[str]]) -> str: if isinstance(response_message, list): @@ -453,9 +467,11 @@ def get_extract_by_id( An IPUMS extract object """ extract_def = self.get_extract_info(extract_id, collection) - if "microdata" in BaseExtract._collection_type_to_extract: - extract = MicrodataExtract(**extract_def["extractDefinition"]) + collection_type = _get_collection_type(extract_def["extractDefinition"]["collection"]) + extract_class = BaseExtract._collection_type_to_extract[collection_type] + extract = extract_class(**extract_def["extractDefinition"]) + return extract def extract_is_expired( diff --git a/src/ipumspy/api/extract.py b/src/ipumspy/api/extract.py index 7c22da3..b431152 100644 --- a/src/ipumspy/api/extract.py +++ b/src/ipumspy/api/extract.py @@ -480,7 +480,7 @@ def __init__( super().__init__() self.collection_type = self.collection_type - """IPUMS Collection type (microdata currently the only valid value)""" + """IPUMS Collection type""" self.collection = collection self.samples = self._validate_list_args(samples, Sample) self.variables = self._validate_list_args(variables, Variable) From 2c0c8c78b338514719d2317a3e763b91aa93396c Mon Sep 17 00:00:00 2001 From: Finn Roberts Date: Thu, 26 Sep 2024 18:01:26 -0400 Subject: [PATCH 04/35] Enable download_extract() for AggregateDataExtract objects Co-authored-by: Ben Levesque --- src/ipumspy/api/core.py | 57 ++++++++++++++++++++++++++++------------- 1 file changed, 39 insertions(+), 18 deletions(-) diff --git a/src/ipumspy/api/core.py b/src/ipumspy/api/core.py index f3e19a4..8feb4a1 100644 --- a/src/ipumspy/api/core.py +++ b/src/ipumspy/api/core.py @@ -264,6 +264,7 @@ def download_extract( extract data file. """ extract_id, collection = _extract_and_collection(extract, collection) + collection_type = _get_collection_type(collection) # if download_dir specified check if it exists download_dir = Path(download_dir or Path.cwd()) @@ -296,25 +297,44 @@ def download_extract( ) download_links = response.json()["downloadLinks"] + try: - # if the extract has been expired, the download_links element will be - # an empty dict - data_url = download_links["data"]["url"] - ddi_url = download_links["ddiCodebook"]["url"] - download_urls = [data_url, ddi_url] - - if stata_command_file: - _url = download_links["stataCommandFile"]["url"] - download_urls.append(_url) - if spss_command_file: - _url = download_links["spssCommandFile"]["url"] - download_urls.append(_url) - if sas_command_file: - _url = download_links["sasCommandFile"]["url"] - download_urls.append(_url) - if r_command_file: - _url = download_links["rCommandFile"]["url"] - download_urls.append(_url) + if collection_type == "aggregate_data": + # Aggregate data links will include a tableData url, gisData url, or both + download_urls = [] + + # If neither tableData nor gisData in download links, extract has likely expired + valid_links = [link in ["tableData", "gisData"] for link in download_links] + + if not any(valid_links): + raise KeyError() # Trigger exception to get consistent error message for aggregate data and microdata + + if "tableData" in download_links: + tabledata_url = download_links["tableData"]["url"] + download_urls.append(tabledata_url) + + if "gisData" in download_links: + gisData_url = download_links["gisData"]["url"] + download_urls.append(gisData_url) + else: + # if the extract has been expired, the download_links element will be + # an empty dict + data_url = download_links["data"]["url"] + ddi_url = download_links["ddiCodebook"]["url"] + download_urls = [data_url, ddi_url] + + if stata_command_file: + _url = download_links["stataCommandFile"]["url"] + download_urls.append(_url) + if spss_command_file: + _url = download_links["spssCommandFile"]["url"] + download_urls.append(_url) + if sas_command_file: + _url = download_links["sasCommandFile"]["url"] + download_urls.append(_url) + if r_command_file: + _url = download_links["rCommandFile"]["url"] + download_urls.append(_url) except KeyError: if isinstance(extract, BaseExtract): @@ -327,6 +347,7 @@ def download_extract( f"IPUMS {collection} extract {extract_id} has expired and its files have been deleted.\n" f"Use `get_extract_by_id()` and `submit_extract()` to resubmit this definition as a new extract request." ) + for url in download_urls: file_name = url.split("/")[-1] download_path = download_dir / file_name From 96401092604876fc7abfd1f018590ac7525c2d96 Mon Sep 17 00:00:00 2001 From: Finn Roberts Date: Mon, 30 Sep 2024 12:35:53 -0400 Subject: [PATCH 05/35] Black --- src/ipumspy/api/core.py | 20 +++++++++----- src/ipumspy/api/extract.py | 53 ++++++++++++++++++++++++++------------ 2 files changed, 50 insertions(+), 23 deletions(-) diff --git a/src/ipumspy/api/core.py b/src/ipumspy/api/core.py index 8feb4a1..b3199bc 100644 --- a/src/ipumspy/api/core.py +++ b/src/ipumspy/api/core.py @@ -64,6 +64,7 @@ def _extract_and_collection( ) return extract_id, collection + def _get_collection_type(collection: str) -> str: collection_types = { "usa": "microdata", @@ -74,11 +75,12 @@ def _get_collection_type(collection: str) -> str: "mtus": "microdata", "nhis": "microdata", "meps:": "microdata", - "nhgis": "aggregate_data" + "nhgis": "aggregate_data", } return collection_types[collection] + def _prettify_message(response_message: Union[str, List[str]]) -> str: if isinstance(response_message, list): return "\n".join(response_message) @@ -304,11 +306,13 @@ def download_extract( download_urls = [] # If neither tableData nor gisData in download links, extract has likely expired - valid_links = [link in ["tableData", "gisData"] for link in download_links] + valid_links = [ + link in ["tableData", "gisData"] for link in download_links + ] if not any(valid_links): - raise KeyError() # Trigger exception to get consistent error message for aggregate data and microdata - + raise KeyError() # Trigger exception to get consistent error message for aggregate data and microdata + if "tableData" in download_links: tabledata_url = download_links["tableData"]["url"] download_urls.append(tabledata_url) @@ -347,7 +351,7 @@ def download_extract( f"IPUMS {collection} extract {extract_id} has expired and its files have been deleted.\n" f"Use `get_extract_by_id()` and `submit_extract()` to resubmit this definition as a new extract request." ) - + for url in download_urls: file_name = url.split("/")[-1] download_path = download_dir / file_name @@ -488,11 +492,13 @@ def get_extract_by_id( An IPUMS extract object """ extract_def = self.get_extract_info(extract_id, collection) - collection_type = _get_collection_type(extract_def["extractDefinition"]["collection"]) + collection_type = _get_collection_type( + extract_def["extractDefinition"]["collection"] + ) extract_class = BaseExtract._collection_type_to_extract[collection_type] extract = extract_class(**extract_def["extractDefinition"]) - + return extract def extract_is_expired( diff --git a/src/ipumspy/api/extract.py b/src/ipumspy/api/extract.py index b431152..841c28c 100644 --- a/src/ipumspy/api/extract.py +++ b/src/ipumspy/api/extract.py @@ -154,15 +154,16 @@ def build(self): return built_dataset + @dataclass class TimeSeriesTable(IpumsObject): """ IPUMS TimeSeriesTable object to include in an AggregateDataExtract object. """ - + name: str """IPUMS NHGIS time series table name/id""" - geog_levels: List[str] # required parameter + geog_levels: List[str] # required parameter """Geographic level(s) at which to obtain data for this time series table""" years: Optional[List[str]] = field(default_factory=list) """Years for which to obtain data for this time series table""" @@ -176,7 +177,8 @@ def build(self): built_tst["years"] = built_tst.pop("years") return built_tst - + + @dataclass class Shapefile(IpumsObject): """ @@ -227,7 +229,11 @@ def _unpack_tuv_dict(dct: dict) -> List[TimeUseVariable]: def _unpack_dataset_dict(dct: dict) -> List[Dataset]: datasets = [] for dataset in dct.keys(): - dataset_obj = Dataset(name=dataset, data_tables=dct[dataset]["dataTables"], geog_levels=dct[dataset]["geogLevels"]) + dataset_obj = Dataset( + name=dataset, + data_tables=dct[dataset]["dataTables"], + geog_levels=dct[dataset]["geogLevels"], + ) if "years" in dct[dataset]: dataset_obj.update("years", dct[dataset]["years"]) if "breakdownValues" in dct[dataset]: @@ -235,19 +241,24 @@ def _unpack_dataset_dict(dct: dict) -> List[Dataset]: datasets.append(dataset_obj) return datasets + def _unpack_tst_dict(dct: dict) -> List[TimeSeriesTable]: time_series_tables = [] for time_series_table in dct.keys(): - time_series_table_obj = TimeSeriesTable(name=time_series_table, geog_levels=dct[time_series_table]["geogLevels"]) + time_series_table_obj = TimeSeriesTable( + name=time_series_table, geog_levels=dct[time_series_table]["geogLevels"] + ) if "years" in dct[time_series_table]: time_series_table_obj.update("years", dct[time_series_table]["years"]) time_series_tables.append(time_series_table_obj) - + return time_series_tables + def _unpack_shapefiles_dict(dct: dict) -> List[Shapefile]: return [Shapefile(name=shapefile) for shapefile in dct.keys()] + class BaseExtract: _collection_type_to_extract: Dict[(str, str), Type[BaseExtract]] = {} @@ -359,11 +370,11 @@ def _validate_list_args(self, list_arg, arg_obj): return args elif isinstance(list_arg, dict) and arg_obj is TimeSeriesTable: args = _unpack_tst_dict(list_arg) - return(args) + return args elif isinstance(list_arg, dict) and arg_obj is Shapefile: args = _unpack_shapefiles_dict(list_arg) - return(args) - + return args + # Make sure extracts don't get built with duplicate variables or samples # if the argument is a list of objects, make sure there are not objects with duplicate names elif all(isinstance(i, arg_obj) for i in list_arg): @@ -603,6 +614,7 @@ def select_cases( variable, "case_selections", {"detailed": values} ) + class AggregateDataExtract(BaseExtract, collection_type="aggregate_data"): def __init__( self, @@ -615,7 +627,7 @@ def __init__( # geographic_extents: Optional[List[str]] = None, tst_layout: str = "time_by_column_layout", breakdown_and_data_type_layout: str = "single_file", - **kwargs + **kwargs, ): """ Class for defining an IPUMS NHGIS extract request. @@ -638,12 +650,20 @@ def __init__( self.collection_type = self.collection_type self.datasets = self._validate_list_args(datasets, Dataset) - self.time_series_tables = self._validate_list_args(timeSeriesTables, TimeSeriesTable) - self.shapefiles = self._validate_list_args(shapefiles, Shapefile) + self.time_series_tables = self._validate_list_args( + timeSeriesTables, TimeSeriesTable + ) + self.shapefiles = self._validate_list_args(shapefiles, Shapefile) + + if ( + len(self.datasets) == 0 + and len(self.time_series_tables) == 0 + and len(self.shapefiles) == 0 + ): + raise ValueError( + "At least one dataset, time series table, or shapefile must be specified." + ) - if len(self.datasets) == 0 and len(self.time_series_tables) == 0 and len(self.shapefiles) == 0: - raise ValueError("At least one dataset, time series table, or shapefile must be specified.") - self.description = description self.data_format = data_format self.breakdown_and_data_type_layout = breakdown_and_data_type_layout @@ -686,12 +706,13 @@ def build(self) -> Dict[str, Any]: tst.name.upper(): tst.build() for tst in self.time_series_tables } built["timeSeriesTableLayout"] = self.time_series_table_layout - + if self.shapefiles is not None: built["shapefiles"] = [shapefile.name for shapefile in self.shapefiles] return built + def extract_from_dict(dct: Dict[str, Any]) -> Union[BaseExtract, List[BaseExtract]]: """ Convert an extract that is currently specified as a dictionary (usually from a file) From 77ce540ef95d0e52c15b51e4ac492f6dee6745c0 Mon Sep 17 00:00:00 2001 From: Finn Roberts Date: Tue, 1 Oct 2024 10:05:07 -0400 Subject: [PATCH 06/35] Use snake case for all extract args --- src/ipumspy/api/extract.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ipumspy/api/extract.py b/src/ipumspy/api/extract.py index 841c28c..b7f25c9 100644 --- a/src/ipumspy/api/extract.py +++ b/src/ipumspy/api/extract.py @@ -620,7 +620,7 @@ def __init__( self, collection: str, datasets: Optional[Union[List[str], List[Dataset]]] = [], - timeSeriesTables: Optional[Union[List[str], List[TimeSeriesTable]]] = [], + time_series_tables: Optional[Union[List[str], List[TimeSeriesTable]]] = [], shapefiles: Optional[Union[List[str], List[Shapefile]]] = [], description: str = "", data_format: str = "csv_no_header", @@ -651,7 +651,7 @@ def __init__( self.datasets = self._validate_list_args(datasets, Dataset) self.time_series_tables = self._validate_list_args( - timeSeriesTables, TimeSeriesTable + time_series_tables, TimeSeriesTable ) self.shapefiles = self._validate_list_args(shapefiles, Shapefile) From 5d4c15c0095ded40f96d2d17d577d5ce9fb21c49 Mon Sep 17 00:00:00 2001 From: Finn Roberts Date: Wed, 2 Oct 2024 15:15:17 -0400 Subject: [PATCH 07/35] Make `geographic_extents` an explicit argument --- src/ipumspy/api/extract.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/ipumspy/api/extract.py b/src/ipumspy/api/extract.py index b7f25c9..f49f879 100644 --- a/src/ipumspy/api/extract.py +++ b/src/ipumspy/api/extract.py @@ -624,7 +624,7 @@ def __init__( shapefiles: Optional[Union[List[str], List[Shapefile]]] = [], description: str = "", data_format: str = "csv_no_header", - # geographic_extents: Optional[List[str]] = None, + geographic_extents: Optional[List[str]] = None, tst_layout: str = "time_by_column_layout", breakdown_and_data_type_layout: str = "single_file", **kwargs, @@ -638,6 +638,9 @@ def __init__( shapefiles: list of shapefile IDs from IPUMS NHGIS description: short description of your extract data_format: desired format of the extract data file. One of ``"csv_no_header"``, ``"csv_header"``, or ``"fixed_width"``. + geographic_extents: Geographic extents to use for all ``datasets`` in the extract definition (for instance, to + to obtain data within a particular state). Use ``*`` to select all available extents. Note that + not all geographic levels support extent selection. breakdown_and_data_type_layout: desired layout of any `datasets` that have multiple data types or breakdown values. Either ``"single_file"`` (default) or ``"separate files"`` time_series_table_layout: desired data layout for all ``time_series_tables`` in the extract definition. @@ -666,6 +669,7 @@ def __init__( self.description = description self.data_format = data_format + self.geographic_extents = geographic_extents self.breakdown_and_data_type_layout = breakdown_and_data_type_layout self.time_series_table_layout = tst_layout @@ -701,6 +705,9 @@ def build(self) -> Dict[str, Any]: } built["breakdownAndDataTypeLayout"] = self.breakdown_and_data_type_layout + if self.geographic_extents is not None: + built["geographicExtents"] = self.geographic_extents + if self.time_series_tables is not None: built["timeSeriesTables"] = { tst.name.upper(): tst.build() for tst in self.time_series_tables From eee0e54949edf4fafe360118422f0b81a2865bfa Mon Sep 17 00:00:00 2001 From: Finn Roberts Date: Wed, 2 Oct 2024 15:16:01 -0400 Subject: [PATCH 08/35] Fix `tst_layout` argument name --- src/ipumspy/api/extract.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/ipumspy/api/extract.py b/src/ipumspy/api/extract.py index f49f879..cf2f956 100644 --- a/src/ipumspy/api/extract.py +++ b/src/ipumspy/api/extract.py @@ -641,10 +641,10 @@ def __init__( geographic_extents: Geographic extents to use for all ``datasets`` in the extract definition (for instance, to to obtain data within a particular state). Use ``*`` to select all available extents. Note that not all geographic levels support extent selection. + tst_layout: desired data layout for all ``time_series_tables`` in the extract definition. + One of ``"time_by_column_layout"``, ``"time_by_row_layout"``, or ``"time_by_file_layout"`` breakdown_and_data_type_layout: desired layout of any `datasets` that have multiple data types or breakdown values. Either ``"single_file"`` (default) or ``"separate files"`` - time_series_table_layout: desired data layout for all ``time_series_tables`` in the extract definition. - One of ``"time_by_column_layout"``, ``"time_by_row_layout"``, or ``"time_by_file_layout"``. """ super().__init__() @@ -671,8 +671,8 @@ def __init__( self.data_format = data_format self.geographic_extents = geographic_extents self.breakdown_and_data_type_layout = breakdown_and_data_type_layout - self.time_series_table_layout = tst_layout - + self.tst_layout = tst_layout + self.api_version = ( self.extract_api_version(kwargs) if len(kwargs.keys()) > 0 @@ -712,7 +712,7 @@ def build(self) -> Dict[str, Any]: built["timeSeriesTables"] = { tst.name.upper(): tst.build() for tst in self.time_series_tables } - built["timeSeriesTableLayout"] = self.time_series_table_layout + built["timeSeriesTableLayout"] = self.tst_layout if self.shapefiles is not None: built["shapefiles"] = [shapefile.name for shapefile in self.shapefiles] From e2b05197d0e8423bf5428738813eefd9d4be5a62 Mon Sep 17 00:00:00 2001 From: Finn Roberts Date: Wed, 9 Oct 2024 08:45:32 -0400 Subject: [PATCH 09/35] Enable AggregateDataExtract creation from dict --- src/ipumspy/api/core.py | 18 +----------------- src/ipumspy/api/extract.py | 38 ++++++++++++++++++++++++++++++-------- 2 files changed, 31 insertions(+), 25 deletions(-) diff --git a/src/ipumspy/api/core.py b/src/ipumspy/api/core.py index b3199bc..7a48854 100644 --- a/src/ipumspy/api/core.py +++ b/src/ipumspy/api/core.py @@ -25,7 +25,7 @@ IpumsTimeoutException, TransientIpumsApiException, ) -from .extract import BaseExtract, MicrodataExtract +from .extract import BaseExtract, MicrodataExtract, _get_collection_type class ModifiedIpumsExtract(Warning): @@ -65,22 +65,6 @@ def _extract_and_collection( return extract_id, collection -def _get_collection_type(collection: str) -> str: - collection_types = { - "usa": "microdata", - "cps": "microdata", - "ipumsi": "microdata", - "atus": "microdata", - "ahtus": "microdata", - "mtus": "microdata", - "nhis": "microdata", - "meps:": "microdata", - "nhgis": "aggregate_data", - } - - return collection_types[collection] - - def _prettify_message(response_message: Union[str, List[str]]) -> str: if isinstance(response_message, list): return "\n".join(response_message) diff --git a/src/ipumspy/api/extract.py b/src/ipumspy/api/extract.py index cf2f956..6e2275d 100644 --- a/src/ipumspy/api/extract.py +++ b/src/ipumspy/api/extract.py @@ -259,6 +259,22 @@ def _unpack_shapefiles_dict(dct: dict) -> List[Shapefile]: return [Shapefile(name=shapefile) for shapefile in dct.keys()] +def _get_collection_type(collection: str) -> str: + collection_types = { + "usa": "microdata", + "cps": "microdata", + "ipumsi": "microdata", + "atus": "microdata", + "ahtus": "microdata", + "mtus": "microdata", + "nhis": "microdata", + "meps:": "microdata", + "nhgis": "aggregate_data", + } + + return collection_types[collection] + + class BaseExtract: _collection_type_to_extract: Dict[(str, str), Type[BaseExtract]] = {} @@ -746,19 +762,25 @@ def _camel_to_snake(key): return snake def _make_snake_ext(ext_dict): + obj_keys = ["variables", "samples", "timeUseVariables", "datasets", "timeSeriesTables"] + for key in ext_dict.keys(): if isinstance(ext_dict[key], dict): - if key not in ["variables", "samples", "timeUseVariables"]: + if key not in obj_keys: ext_dict[key] = _make_snake_ext(ext_dict[key]) return {_camel_to_snake(k): v for k, v in ext_dict.items()} - ext_dict = _make_snake_ext(dct) - # XXX To Do: When MicrodataExtract is no longer the only extract class, - # this method will need to differentiate between the different collection types - # since this info isn't available from the api response and so won't be stored in any - # dict representation, there needs to be a way to know which collections are micro - # and which are not. - return MicrodataExtract(**ext_dict) + ext_dct = _make_snake_ext(dct) + collection_type = _get_collection_type(dct["collection"]) + + if collection_type == "microdata": + extract = MicrodataExtract(**ext_dct) + elif collection_type == "aggregate_data": + extract = AggregateDataExtract(**ext_dct) + else: + raise NotImplementedError("Unrecognized IPUMS collection") + + return extract def extract_to_dict(extract: Union[BaseExtract, List[BaseExtract]]) -> Dict[str, Any]: From 28f71672de59c92d7021a530d63c69319fdd2e60 Mon Sep 17 00:00:00 2001 From: Finn Roberts Date: Wed, 9 Oct 2024 16:31:17 -0400 Subject: [PATCH 10/35] Add unit tests for aggregate data handling --- .../test_agg_data_feature_errors.yaml | 68 +++++ .../test_api/test_nhgis_download_extract.yaml | 254 ++++++++++++++++++ .../test_api/test_nhgis_submit_extract.yaml | 122 +++++++++ tests/fixtures/nhgis_extract_v2.pkl | Bin 0 -> 1341 bytes tests/fixtures/nhgis_extract_v2.yml | 25 ++ tests/test_api.py | 174 ++++++++++++ 6 files changed, 643 insertions(+) create mode 100644 tests/cassettes/test_api/test_agg_data_feature_errors.yaml create mode 100644 tests/cassettes/test_api/test_nhgis_download_extract.yaml create mode 100644 tests/cassettes/test_api/test_nhgis_submit_extract.yaml create mode 100644 tests/fixtures/nhgis_extract_v2.pkl create mode 100644 tests/fixtures/nhgis_extract_v2.yml diff --git a/tests/cassettes/test_api/test_agg_data_feature_errors.yaml b/tests/cassettes/test_api/test_agg_data_feature_errors.yaml new file mode 100644 index 0000000..e179c59 --- /dev/null +++ b/tests/cassettes/test_api/test_agg_data_feature_errors.yaml @@ -0,0 +1,68 @@ +interactions: +- request: + body: '{"description": "", "dataFormat": "csv_no_header", "collection": "nhgis", + "version": 2, "datasets": {"a": {"dataTables": "b", "geogLevels": "c", "years": + "d", "breakdownValues": []}}, "breakdownAndDataTypeLayout": "single_file", "timeSeriesTables": + {"A": {"geogLevels": "b", "years": []}}, "timeSeriesTableLayout": "time_by_column_layout", + "shapefiles": []}' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '357' + Content-Type: + - application/json + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: POST + uri: https://api.ipums.org/extracts?collection=nhgis&version=2 + response: + body: + string: '{"type":"SchemaValidationError","status":{"code":400,"name":"Bad Request"},"detail":["The + property ''#/datasets/a/dataTables'' of type string did not match the following + type: array.","The property ''#/datasets/a/geogLevels'' of type string did + not match the following type: array.","The property ''#/datasets/a/years'' + of type string did not match the following type: array.","The property ''#/timeSeriesTables/A/geogLevels'' + of type string did not match the following type: array."]}' + headers: + Cache-Control: + - no-cache + Content-Length: + - '477' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 04 Oct 2024 15:35:49 GMT + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + - Accept + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - 24ab0402-56cf-4691-a45a-050c64cedffa + X-Runtime: + - '0.129516' + X-Xss-Protection: + - '0' + status: + code: 400 + message: Bad Request +version: 1 diff --git a/tests/cassettes/test_api/test_nhgis_download_extract.yaml b/tests/cassettes/test_api/test_nhgis_download_extract.yaml new file mode 100644 index 0000000..c1a41c3 --- /dev/null +++ b/tests/cassettes/test_api/test_nhgis_download_extract.yaml @@ -0,0 +1,254 @@ +interactions: +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/extracts/1383?collection=nhgis&version=2 + response: + body: + string: '{"number":1383,"status":"completed","email":"robe2037@umn.edu","downloadLinks":{"codebookPreview":{"url":"https://api.ipums.org/downloads/nhgis/api/v1/extracts/2366084/nhgis1383_csv_PREVIEW.zip","bytes":2216,"sha256":null},"tableData":{"url":"https://api.ipums.org/downloads/nhgis/api/v1/extracts/2366084/nhgis1383_csv.zip","bytes":5253,"sha256":null}},"extractDefinition":{"dataFormat":"csv_no_header","description":"Simple + extract for ipumspy unit testing","datasets":{"1990_STF1":{"dataTables":["NP1"],"geogLevels":["state"],"breakdownValues":["bs09.ge00"]}},"timeSeriesTableLayout":"time_by_column_layout","version":2,"collection":"nhgis"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '644' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 04 Oct 2024 19:36:04 GMT + Etag: + - W/"d175682a29fda3a5ae67d95e7c98e5c6" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + - Accept + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - 8a3e7af2-b244-42c6-a884-d152d052dc09 + X-Runtime: + - '0.718477' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/extracts/1383?collection=nhgis&version=2 + response: + body: + string: '{"number":1383,"status":"completed","email":"robe2037@umn.edu","downloadLinks":{"codebookPreview":{"url":"https://api.ipums.org/downloads/nhgis/api/v1/extracts/2366084/nhgis1383_csv_PREVIEW.zip","bytes":2216,"sha256":null},"tableData":{"url":"https://api.ipums.org/downloads/nhgis/api/v1/extracts/2366084/nhgis1383_csv.zip","bytes":5253,"sha256":null}},"extractDefinition":{"dataFormat":"csv_no_header","description":"Simple + extract for ipumspy unit testing","datasets":{"1990_STF1":{"dataTables":["NP1"],"geogLevels":["state"],"breakdownValues":["bs09.ge00"]}},"timeSeriesTableLayout":"time_by_column_layout","version":2,"collection":"nhgis"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '644' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 04 Oct 2024 19:36:05 GMT + Etag: + - W/"d175682a29fda3a5ae67d95e7c98e5c6" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + - Accept + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - 9f25de13-a938-4305-8fca-5085cb4a2c7a + X-Runtime: + - '0.706547' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/downloads/nhgis/api/v1/extracts/2366084/nhgis1383_csv.zip + response: + body: + string: !!binary | + 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 + WxUK9UYjbSLf3MAvxFvLDwdSo8EpMB+JynsPKLZcbnmDT0a4ahJd9Fjgjt9crMjU7lakssq+kwKY + R+XbUwFTYOhqtROd5hC2Qsi0cGJUjC6trzDKPM5hTZ2L/x4hhsXqn7Biq8yZBh5IO6bTfZLFZhX3 + 7ByZFk26WnY5OWghy8t1Emy60ZzoUtV0eXvMZt7ul88EJdOlADnCO/q02tGNqORVYPJQEruKDkig + wpq+y8hlCuKiCYiu2fLu2J3y3Wr7AzYWP+lyd4u6aExOGjhsIGhM34nNl1toDpnvko3gkuV9QKPz + NDkUXVPl3bEryLt2+72NRKOrSwrQrmApNQ6LrQ7rQBWcLykMFriEx0FapM2FMjARMgKqLeb9sW8m + 7pewiuvv+0DKulyLWphurdI1Dn0rYSILd8KT1JJBj+slp/1Pv38PgJE3xpO51DVT3veY8E6W7le7 + /UmdqkKVRkinuE9Dj80Ftsn1nEjrE+oqUmM7LEuwBLl+nJTYkj3RNVfeH1vF79vd42a9a0O6yndL + iqxAljuBzZfFNamOTi0FaxJFiFZMzIMqHa4cHFQwmLpmyPseg9kZtfvfmx+oCQEql7fkVfGRyzcm + yvGwJ73HZnuJ6dbpSxzj6D6+FE+jn9wf/wdQSwECNAMUAAAACAB6nERZWClWMMgHAAD5FAAANQAA + AAAAAAABAAAAtoEAAAAAbmhnaXMxMzgzX2Nzdi9uaGdpczEzODNfZHMxMjBfMTk5MF9zdGF0ZV9j + b2RlYm9vay50eHRQSwECNAMUAAAACACAnERZrPhd1iULAABTHgAALAAAAAAAAAABAAAAtoEvCAAA + bmhnaXMxMzgzX2Nzdi9uaGdpczEzODNfZHMxMjBfMTk5MF9zdGF0ZS5jc3ZQSwUGAAAAAAIAAgC9 + AAAAshMAAAAA + headers: + Accept-Ranges: + - bytes + Cache-Control: + - no-cache + Content-Disposition: + - attachment; filename="nhgis1383_csv.zip"; filename*=UTF-8''nhgis1383_csv.zip + Content-Length: + - '5253' + Content-Type: + - application/zip + Date: + - Fri, 04 Oct 2024 19:36:05 GMT + Etag: + - '"670043a0-1485"' + Last-Modified: + - Fri, 04 Oct 2024 19:36:00 GMT + Server: + - nginx/1.18.0 + Set-Cookie: + - _session_id=80MgBNGNoEsl4d0755ew5sjpGYmAIp2u6zpvy0crOCO8IEqgmXakHaOvrivx9SUiZREbdmTE9dtXoYJxptVvEucw9aOUvwVsyDkg0T2hFNEnvPTM7jVLV9ofEKJM%2BUgq4mNYM0kYpcWcmXjmZaQTzyCW6kfgmxMpEOP%2FtWPS6JKTkR01LMetkDuwodqw2lZKMOREBP%2BfISRBRhvFoTgwkCVb2sBBml7lM%2FBq646uo2w6V8rhvPR%2FnULe6pFEykM%3D--OTyrjzU2JiYcFvtL--e0z4GR7m2xcW4RixkZDNdg%3D%3D; + path=/; secure; HttpOnly; SameSite=None + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_api/test_nhgis_submit_extract.yaml b/tests/cassettes/test_api/test_nhgis_submit_extract.yaml new file mode 100644 index 0000000..36446c3 --- /dev/null +++ b/tests/cassettes/test_api/test_nhgis_submit_extract.yaml @@ -0,0 +1,122 @@ +interactions: +- request: + body: '{"description": "Simple extract for ipumspy unit testing", "dataFormat": + "csv_no_header", "collection": "nhgis", "version": 2, "datasets": {"1990_STF1": + {"dataTables": ["NP1"], "geogLevels": ["state"], "years": [], "breakdownValues": + []}}, "breakdownAndDataTypeLayout": "single_file", "timeSeriesTables": {}, "timeSeriesTableLayout": + "time_by_column_layout", "shapefiles": []}' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '376' + Content-Type: + - application/json + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: POST + uri: https://api.ipums.org/extracts?collection=nhgis&version=2 + response: + body: + string: '{"number":1383,"status":"queued","email":"robe2037@umn.edu","downloadLinks":{},"extractDefinition":{"dataFormat":"csv_no_header","description":"Simple + extract for ipumspy unit testing","datasets":{"1990_STF1":{"dataTables":["NP1"],"geogLevels":["state"],"years":[],"breakdownValues":[]}},"timeSeriesTables":{},"timeSeriesTableLayout":"time_by_column_layout","shapefiles":[],"breakdownAndDataTypeLayout":"single_file","version":2,"collection":"nhgis"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '451' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 04 Oct 2024 19:35:40 GMT + Etag: + - W/"877bff7543fd0c0437f90315b1b649dd" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + - Accept + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - b2ff72dd-693d-4516-a3ea-4ed25a152fd2 + X-Runtime: + - '0.441034' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/extracts/1383?collection=nhgis&version=2 + response: + body: + string: '{"number":1383,"status":"started","email":"robe2037@umn.edu","downloadLinks":{},"extractDefinition":{"dataFormat":"csv_no_header","description":"Simple + extract for ipumspy unit testing","datasets":{"1990_STF1":{"dataTables":["NP1"],"geogLevels":["state"],"breakdownValues":["bs09.ge00"]}},"timeSeriesTableLayout":"time_by_column_layout","version":2,"collection":"nhgis"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '371' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 04 Oct 2024 19:35:40 GMT + Etag: + - W/"3ba17ec59179f904d9507cb8141caec5" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + - Accept + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - fcb99e9b-720b-413d-9dac-ccc4c3c4d07e + X-Runtime: + - '0.748620' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +version: 1 diff --git a/tests/fixtures/nhgis_extract_v2.pkl b/tests/fixtures/nhgis_extract_v2.pkl new file mode 100644 index 0000000000000000000000000000000000000000..2f8e5fd9806403643de78bfe6094aedb74e38fb9 GIT binary patch literal 1341 zcmb7EO>fgc5Uom+l0?u_iV#Skh*P;lNqVSyqoS0erWGhr5r=BEjwkl2_BwcXQ*x*T z_qnnBUuHLnlSVBcK3Mj6=grJ}Z~T4jSG%<0FMKS9CRM|6gAYXmzUiFzwZ-j&D9Rz? z8VGqrv{-_ai^Sq) zo(0fu?(V%Yscb-KEItUcE16_G>+Wp}SdAcyIxvF7eZ+DK8VCMFGm)ZO?KIZfcQ27jgk=+T=BL z_xAIy$67|fH8&g;Rrq8nvBSriAqIV%1!LCF5)y+Y(|A?Id`Qesc))F=m~V}>_J}T> zw3}`kd|r%uAVcSp-gpQz?3xlXN`N^Oi|ZrEl~eI#m2}IJ1o{)DN-~awvc_PI?8p9c z3^(SvOj9+B5Bbce%t^bbFi9A%;JWLK6t{(U*CbCVb3uHv4;^Cq4(V%7dY1fA`^|z3 zHo9H>AU>WfPkgfIgZQ*C!KXzN^vDF~%O(f_x15+vc^k|-ra?D)H| 0 + +@pytest.mark.vcr +def test_nhgis_download_extract(live_api_client: IpumsApiClient, tmpdir: Path, ): + live_api_client.download_extract( + collection="nhgis", extract="1383", download_dir=tmpdir # TODO: generalize approach to avoid extract number? + ) + assert (tmpdir / "nhgis1383_csv.zip").exists() + +def test_nhgis_extract_from_dict(fixtures_path: Path): + with open(fixtures_path / "nhgis_extract_v2.yml") as infile: + extract = extract_from_dict(yaml.safe_load(infile)) + + assert isinstance(extract[0], AggregateDataExtract) + + for item in extract: + assert item.collection == "nhgis" + assert item.datasets == [ + Dataset(name = "1990_STF1", data_tables = ["NP1", "NP2"], geog_levels = ["county"]), + Dataset(name = "2010_SF1a", data_tables = ["P1"], geog_levels = ["state"]) + ] + assert item.time_series_tables == [ + TimeSeriesTable(name = "CW3", geog_levels = ["state"], years = ["1990"]) + ] + assert item.tst_layout == "time_by_column_layout" + assert item.shapefiles == [Shapefile(name = "us_state_1790_tl2000")] + assert item.data_format == "csv_header" + assert item.api_version == 2 + +def test_nhgis_extract_to_dict(fixtures_path: Path): + with open(fixtures_path / "nhgis_extract_v2.pkl", "rb") as infile: + extract = pickle.load(infile) + + # export extract to dict + dct = extract_to_dict(extract) + + assert dct["collection"] == "nhgis" + assert dct["datasets"] == { + '2010_SF1a': { + 'dataTables': ['P1'], + 'geogLevels': ['state'], + 'years': [], + 'breakdownValues': ['bs32.ge00'] + }, + '1990_STF1': { + 'dataTables': ['NP1', 'NP2'], + 'geogLevels': ['county'], + 'years': [], + 'breakdownValues': ['bs09.ge00'] + }, + } + assert dct["timeSeriesTables"] == { + "CW3": { + "geogLevels": ["state"], + "years": ["1990"] + } + } + assert dct["version"] == 2 + assert dct["shapefiles"] == ["us_state_1790_tl2000"] + assert dct["dataFormat"] == "csv_header" + assert dct["timeSeriesTableLayout"] == "time_by_column_layout" From 533e7002bd86a4dc2604853c5aac9f1daedb6497 Mon Sep 17 00:00:00 2001 From: Finn Roberts Date: Wed, 9 Oct 2024 16:46:06 -0400 Subject: [PATCH 11/35] Fixes #99; resolve case discrepancies in `get_extract_by_id()` --- src/ipumspy/api/core.py | 9 ++------- src/ipumspy/api/extract.py | 8 ++------ tests/test_api.py | 2 ++ 3 files changed, 6 insertions(+), 13 deletions(-) diff --git a/src/ipumspy/api/core.py b/src/ipumspy/api/core.py index 7a48854..4f729af 100644 --- a/src/ipumspy/api/core.py +++ b/src/ipumspy/api/core.py @@ -25,7 +25,7 @@ IpumsTimeoutException, TransientIpumsApiException, ) -from .extract import BaseExtract, MicrodataExtract, _get_collection_type +from .extract import BaseExtract, MicrodataExtract, _get_collection_type, extract_from_dict class ModifiedIpumsExtract(Warning): @@ -476,12 +476,7 @@ def get_extract_by_id( An IPUMS extract object """ extract_def = self.get_extract_info(extract_id, collection) - collection_type = _get_collection_type( - extract_def["extractDefinition"]["collection"] - ) - - extract_class = BaseExtract._collection_type_to_extract[collection_type] - extract = extract_class(**extract_def["extractDefinition"]) + extract = extract_from_dict(extract_def["extractDefinition"]) return extract diff --git a/src/ipumspy/api/extract.py b/src/ipumspy/api/extract.py index 6e2275d..bc89350 100644 --- a/src/ipumspy/api/extract.py +++ b/src/ipumspy/api/extract.py @@ -773,12 +773,8 @@ def _make_snake_ext(ext_dict): ext_dct = _make_snake_ext(dct) collection_type = _get_collection_type(dct["collection"]) - if collection_type == "microdata": - extract = MicrodataExtract(**ext_dct) - elif collection_type == "aggregate_data": - extract = AggregateDataExtract(**ext_dct) - else: - raise NotImplementedError("Unrecognized IPUMS collection") + extract_class = BaseExtract._collection_type_to_extract[collection_type] + extract = extract_class(**ext_dct) return extract diff --git a/tests/test_api.py b/tests/test_api.py index 1fb255a..64a8b56 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -1248,6 +1248,7 @@ def test_get_extract_by_id(live_api_client: IpumsApiClient): }, "collection": "cps", "version": 2, + "caseSelectWho": "individuals", } ipumsi_ext = live_api_client.get_extract_by_id(6, "ipumsi") @@ -1315,6 +1316,7 @@ def test_get_extract_by_id(live_api_client: IpumsApiClient): }, "collection": "ipumsi", "version": 2, + "caseSelectWho": "individuals", } # extract with warnings From e62d4e0cb66ab10626e179df265d565fbec3ea28 Mon Sep 17 00:00:00 2001 From: Finn Roberts Date: Thu, 10 Oct 2024 07:55:45 -0400 Subject: [PATCH 12/35] Force uppercase TST names --- src/ipumspy/api/extract.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/ipumspy/api/extract.py b/src/ipumspy/api/extract.py index bc89350..48f012e 100644 --- a/src/ipumspy/api/extract.py +++ b/src/ipumspy/api/extract.py @@ -168,6 +168,9 @@ class TimeSeriesTable(IpumsObject): years: Optional[List[str]] = field(default_factory=list) """Years for which to obtain data for this time series table""" + def __post_init__(self): + self.name = self.name.upper() + def build(self): built_tst = self.__dict__.copy() # don't repeat the time series table name From b7ef86225756ed49589bb4cf9731558eff4fcbe3 Mon Sep 17 00:00:00 2001 From: Finn Roberts Date: Thu, 10 Oct 2024 14:25:07 -0400 Subject: [PATCH 13/35] Generalize extract dictionary submission --- src/ipumspy/api/core.py | 12 ++-- .../test_api/test_submit_extract_dict.yaml | 65 +++++++++++++++++++ tests/test_api.py | 20 ++++++ 3 files changed, 90 insertions(+), 7 deletions(-) create mode 100644 tests/cassettes/test_api/test_submit_extract_dict.yaml diff --git a/src/ipumspy/api/core.py b/src/ipumspy/api/core.py index 4f729af..ed13e53 100644 --- a/src/ipumspy/api/core.py +++ b/src/ipumspy/api/core.py @@ -152,29 +152,27 @@ def post(self, *args, **kwargs) -> requests.Response: def submit_extract( self, extract: Union[BaseExtract, Dict[str, Any]], - collection: Optional[str] = None, ) -> BaseExtract: """ Submit an extract request to the IPUMS API Args: - extract: The extract description to submit. May be either an - ``IpumsExtract`` object, or the ``details`` of such an - object, in which case it must include a key named ``collection`` + extract: The extract description to submit. May be either an object + inheriting from ``BaseExtract``, or a dictionary containing the extract + definition. Returns: The number of the extract for the passed user account """ - if not isinstance(extract, BaseExtract): extract = copy.deepcopy(extract) - if "microdata" in BaseExtract._collection_type_to_extract: - extract = MicrodataExtract(extract) + extract = extract_from_dict(extract) # if no api version was provided on instantiation of extract object # or in extract definition dict, assign it to the default if extract.api_version is None: extract.api_version = self.api_version + response = self.post( f"{self.base_url}/extracts", params={"collection": extract.collection, "version": extract.api_version}, diff --git a/tests/cassettes/test_api/test_submit_extract_dict.yaml b/tests/cassettes/test_api/test_submit_extract_dict.yaml new file mode 100644 index 0000000..394c271 --- /dev/null +++ b/tests/cassettes/test_api/test_submit_extract_dict.yaml @@ -0,0 +1,65 @@ +interactions: +- request: + body: '{"description": "My IPUMS USA extract", "dataFormat": "fixed_width", "dataStructure": + {"hierarchical": {}}, "samples": {"us2017a": {}}, "variables": {"AGE": {"preselected": + false, "caseSelections": {}, "attachedCharacteristics": [], "dataQualityFlags": + false}}, "collection": "usa", "version": 2}' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '296' + Content-Type: + - application/json + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: POST + uri: https://api.ipums.org/extracts?collection=usa&version=2 + response: + body: + string: '{"number":417,"status":"queued","email":"robe2037@umn.edu","downloadLinks":{},"extractDefinition":{"version":2,"dataStructure":{"hierarchical":{}},"dataFormat":"fixed_width","caseSelectWho":"individuals","description":"My + IPUMS USA extract","samples":{"us2017a":{}},"variables":{"RECTYPE":{},"YEAR":{"preselected":true},"SAMPLE":{"preselected":true},"SERIAL":{"preselected":true},"CBSERIAL":{"preselected":true},"HHWT":{"preselected":true},"CLUSTER":{"preselected":true},"STRATA":{"preselected":true},"GQ":{"preselected":true},"PERNUM":{"preselected":true},"PERWT":{"preselected":true},"AGE":{}},"collection":"usa"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '616' + Content-Type: + - application/json; charset=utf-8 + Date: + - Thu, 10 Oct 2024 18:24:43 GMT + Etag: + - W/"8a8924f9ff94f2543d9629f891197846" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + - Accept + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - aaa058c3-4e1c-4f65-a480-23c8241952cb + X-Runtime: + - '0.937330' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +version: 1 diff --git a/tests/test_api.py b/tests/test_api.py index 64a8b56..0a148c6 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -812,6 +812,26 @@ def test_submit_extract_live(live_api_client: IpumsApiClient): assert live_api_client.extract_status(extract) == "queued" +@pytest.mark.vcr +def test_submit_extract_dict(live_api_client: IpumsApiClient): + """ + Confirm that we can submit an extract as a dictionary + """ + extract_dict = { + "collection": "usa", + "version": 2, + "samples": {"us2017a": {}}, + "variables": {"AGE": {}}, + "data_structure": {"hierarchical": {}} + } + + extract = live_api_client.submit_extract(extract_dict) + + assert extract._info["status"] == "queued" + assert Variable("AGE") in extract.variables + assert Sample("us2017a") in extract.samples + assert extract_dict["data_structure"] == extract.data_structure + @pytest.mark.vcr def test_submit_hierarchical_extract_live(live_api_client: IpumsApiClient): """ From 78169beab6a7d15a60d60a127408bf4d7cb4b363 Mon Sep 17 00:00:00 2001 From: Finn Roberts Date: Tue, 15 Oct 2024 14:12:01 -0400 Subject: [PATCH 14/35] Force uppercase TST names --- src/ipumspy/api/extract.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ipumspy/api/extract.py b/src/ipumspy/api/extract.py index 48f012e..38fba7c 100644 --- a/src/ipumspy/api/extract.py +++ b/src/ipumspy/api/extract.py @@ -170,6 +170,7 @@ class TimeSeriesTable(IpumsObject): def __post_init__(self): self.name = self.name.upper() + self.years = [str(yr) for yr in self.years] def build(self): built_tst = self.__dict__.copy() From 1b4f7e8756c8180ff073f17f51c15c29dbe06d5d Mon Sep 17 00:00:00 2001 From: Finn Roberts Date: Tue, 15 Oct 2024 15:08:33 -0400 Subject: [PATCH 15/35] Organize tests --- ...rs.yaml => test_nhgis_feature_errors.yaml} | 6 +- ...ml => test_nhgis_submit_extract_live.yaml} | 66 +--- tests/test_api.py | 361 ++++++++++-------- 3 files changed, 200 insertions(+), 233 deletions(-) rename tests/cassettes/test_api/{test_agg_data_feature_errors.yaml => test_nhgis_feature_errors.yaml} (95%) rename tests/cassettes/test_api/{test_nhgis_submit_extract.yaml => test_nhgis_submit_extract_live.yaml} (52%) diff --git a/tests/cassettes/test_api/test_agg_data_feature_errors.yaml b/tests/cassettes/test_api/test_nhgis_feature_errors.yaml similarity index 95% rename from tests/cassettes/test_api/test_agg_data_feature_errors.yaml rename to tests/cassettes/test_api/test_nhgis_feature_errors.yaml index e179c59..e73f2bc 100644 --- a/tests/cassettes/test_api/test_agg_data_feature_errors.yaml +++ b/tests/cassettes/test_api/test_nhgis_feature_errors.yaml @@ -36,7 +36,7 @@ interactions: Content-Type: - application/json; charset=utf-8 Date: - - Fri, 04 Oct 2024 15:35:49 GMT + - Tue, 15 Oct 2024 18:58:12 GMT Referrer-Policy: - strict-origin-when-cross-origin Server: @@ -57,9 +57,9 @@ interactions: X-Ratelimit-Reset: - '0' X-Request-Id: - - 24ab0402-56cf-4691-a45a-050c64cedffa + - 724acdfd-3070-4605-b01d-00d336e086c2 X-Runtime: - - '0.129516' + - '0.137920' X-Xss-Protection: - '0' status: diff --git a/tests/cassettes/test_api/test_nhgis_submit_extract.yaml b/tests/cassettes/test_api/test_nhgis_submit_extract_live.yaml similarity index 52% rename from tests/cassettes/test_api/test_nhgis_submit_extract.yaml rename to tests/cassettes/test_api/test_nhgis_submit_extract_live.yaml index 36446c3..d11596b 100644 --- a/tests/cassettes/test_api/test_nhgis_submit_extract.yaml +++ b/tests/cassettes/test_api/test_nhgis_submit_extract_live.yaml @@ -22,7 +22,7 @@ interactions: uri: https://api.ipums.org/extracts?collection=nhgis&version=2 response: body: - string: '{"number":1383,"status":"queued","email":"robe2037@umn.edu","downloadLinks":{},"extractDefinition":{"dataFormat":"csv_no_header","description":"Simple + string: '{"number":1400,"status":"queued","email":"robe2037@umn.edu","downloadLinks":{},"extractDefinition":{"dataFormat":"csv_no_header","description":"Simple extract for ipumspy unit testing","datasets":{"1990_STF1":{"dataTables":["NP1"],"geogLevels":["state"],"years":[],"breakdownValues":[]}},"timeSeriesTables":{},"timeSeriesTableLayout":"time_by_column_layout","shapefiles":[],"breakdownAndDataTypeLayout":"single_file","version":2,"collection":"nhgis"}}' headers: Cache-Control: @@ -32,9 +32,9 @@ interactions: Content-Type: - application/json; charset=utf-8 Date: - - Fri, 04 Oct 2024 19:35:40 GMT + - Tue, 15 Oct 2024 18:58:19 GMT Etag: - - W/"877bff7543fd0c0437f90315b1b649dd" + - W/"cb53f9e2e778b021c28c18d42f032ac1" Referrer-Policy: - strict-origin-when-cross-origin Server: @@ -55,65 +55,9 @@ interactions: X-Ratelimit-Reset: - '0' X-Request-Id: - - b2ff72dd-693d-4516-a3ea-4ed25a152fd2 + - a4b3d665-bdad-4e78-9d01-4b9630a517bb X-Runtime: - - '0.441034' - X-Xss-Protection: - - '0' - status: - code: 200 - message: OK -- request: - body: null - headers: - Accept: - - '*/*' - Accept-Encoding: - - gzip, deflate - Connection: - - keep-alive - User-Agent: - - python-ipumspy:0.5.1.github.com/ipums/ipumspy - method: GET - uri: https://api.ipums.org/extracts/1383?collection=nhgis&version=2 - response: - body: - string: '{"number":1383,"status":"started","email":"robe2037@umn.edu","downloadLinks":{},"extractDefinition":{"dataFormat":"csv_no_header","description":"Simple - extract for ipumspy unit testing","datasets":{"1990_STF1":{"dataTables":["NP1"],"geogLevels":["state"],"breakdownValues":["bs09.ge00"]}},"timeSeriesTableLayout":"time_by_column_layout","version":2,"collection":"nhgis"}}' - headers: - Cache-Control: - - max-age=0, private, must-revalidate - Content-Length: - - '371' - Content-Type: - - application/json; charset=utf-8 - Date: - - Fri, 04 Oct 2024 19:35:40 GMT - Etag: - - W/"3ba17ec59179f904d9507cb8141caec5" - Referrer-Policy: - - strict-origin-when-cross-origin - Server: - - nginx/1.22.1 - Vary: - - Origin - - Accept - X-Content-Type-Options: - - nosniff - X-Frame-Options: - - SAMEORIGIN - X-Permitted-Cross-Domain-Policies: - - none - X-Ratelimit-Limit: - - '-1' - X-Ratelimit-Remaining: - - '0' - X-Ratelimit-Reset: - - '0' - X-Request-Id: - - fcb99e9b-720b-413d-9dac-ccc4c3c4d07e - X-Runtime: - - '0.748620' + - '0.521991' X-Xss-Protection: - '0' status: diff --git a/tests/test_api.py b/tests/test_api.py index 0a148c6..5a7c8f9 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -69,6 +69,38 @@ def live_api_client(environment_variables) -> IpumsApiClient: return live_client +@pytest.fixture() +def simple_nhgis_extract() -> AggregateDataExtract: + extract = AggregateDataExtract( + "nhgis", + description = "Simple extract for ipumspy unit testing", + datasets = [Dataset("1990_STF1", ["NP1"], ["state"])] + ) + + return extract + +@pytest.fixture() +def complex_nhgis_extract() -> AggregateDataExtract: + extract = AggregateDataExtract( + "nhgis", + description = "Complex extract for ipumspy unit testing", + datasets = [ + Dataset("2010_SF1a", ["P1", "P2"], ["block"]), + Dataset("2010_SF2a", ["PCT1"], ["state"], breakdown_values = ["bs32.ge89", "bs33.ch002"]) + ], + time_series_tables = [ + TimeSeriesTable("CW3", ["nation", "state"], ["2000"]) + ], + shapefiles = ["us_state_1970_tl2000"], + data_format = "csv_header", + geographic_extents = ["010", "020"], + tst_layout = "time_by_row_layout", + breakdown_and_data_type_layout = "separate_files" + ) + + return extract + + def test_usa_build_extract(): """ Confirm that test extract formatted correctly @@ -346,6 +378,26 @@ def test_attach_characteristics_feature_errors(live_api_client: IpumsApiClient): ) +@pytest.mark.vcr +def test_nhgis_feature_errors(live_api_client: IpumsApiClient): + """ + Test that illegal Dataset and TimeSeriesTable objects throw appropriate errors + """ + extract = AggregateDataExtract( + "nhgis", + datasets = [Dataset("a", "b" ,"c", "d")], + time_series_tables = [TimeSeriesTable("a", "b")] + ) + + with pytest.raises(BadIpumsApiRequest) as exc_info: + live_api_client.submit_extract(extract) + + assert "The property '#/datasets/a/dataTables' of type string did not match the following type: array" in str(exc_info.value) + assert "The property '#/datasets/a/geogLevels' of type string did not match the following type: array" in str(exc_info.value) + assert "The property '#/datasets/a/years' of type string did not match the following type: array" in str(exc_info.value) + assert "The property '#/timeSeriesTables/A/geogLevels' of type string did not match the following type: array" in str(exc_info.value) + + def test_cps_build_extract(): """ Confirm that test extract formatted correctly @@ -541,6 +593,61 @@ def test_atus_build_extract(): ) +def test_nhgis_build_extract(simple_nhgis_extract: AggregateDataExtract, complex_nhgis_extract: AggregateDataExtract): + """ + Test NHGIS extract build structure + """ + assert complex_nhgis_extract.build() == { + "description": "Complex extract for ipumspy unit testing", + "dataFormat": "csv_header", + "collection": "nhgis", + "version": None, + "datasets": { + "2010_SF1a": { + "dataTables": ["P1", "P2"], + "geogLevels": ["block"], + "years": [], + "breakdownValues": [] + }, + "2010_SF2a": { + "dataTables": ["PCT1"], + "geogLevels": ["state"], + "years": [], + "breakdownValues": ["bs32.ge89", "bs33.ch002"] + } + }, + "breakdownAndDataTypeLayout": "separate_files", + "geographicExtents": ["010", "020"], + "timeSeriesTables": { + "CW3": { + "geogLevels": ["nation", "state"], + "years": ["2000"] + } + }, + "timeSeriesTableLayout": "time_by_row_layout", + "shapefiles": ["us_state_1970_tl2000"] + } + + assert simple_nhgis_extract.build() == { + "description": "Simple extract for ipumspy unit testing", + "dataFormat": "csv_no_header", + "collection": "nhgis", + "version": None, + "datasets": { + "1990_STF1": { + "dataTables": ["NP1"], + "geogLevels": ["state"], + "years": [], + "breakdownValues": [] + } + }, + "breakdownAndDataTypeLayout": "single_file", + "timeSeriesTables": {}, + "timeSeriesTableLayout": "time_by_column_layout", + "shapefiles": [] + } + + @pytest.mark.vcr def test_rect_on_nonP_extract(live_api_client: IpumsApiClient): bad_hs_ext = MicrodataExtract( @@ -774,6 +881,30 @@ def test_extract_from_dict(fixtures_path: Path): ) +def test_nhgis_extract_from_dict(fixtures_path: Path): + """ + Test that we can convert extract stored as YAML/dictionary to AggregateDataExtract + """ + with open(fixtures_path / "nhgis_extract_v2.yml") as infile: + extract = extract_from_dict(yaml.safe_load(infile)) + + assert isinstance(extract[0], AggregateDataExtract) + + for item in extract: + assert item.collection == "nhgis" + assert item.datasets == [ + Dataset(name = "1990_STF1", data_tables = ["NP1", "NP2"], geog_levels = ["county"]), + Dataset(name = "2010_SF1a", data_tables = ["P1"], geog_levels = ["state"]) + ] + assert item.time_series_tables == [ + TimeSeriesTable(name = "CW3", geog_levels = ["state"], years = ["1990"]) + ] + assert item.tst_layout == "time_by_column_layout" + assert item.shapefiles == [Shapefile(name = "us_state_1790_tl2000")] + assert item.data_format == "csv_header" + assert item.api_version == 2 + + def test_extract_to_dict(fixtures_path: Path): # reconstitute the extract object from pickle with open(fixtures_path / "usa_00196_extract_obj.pkl", "rb") as infile: @@ -797,6 +928,43 @@ def test_extract_to_dict(fixtures_path: Path): } +def test_nhgis_extract_to_dict(fixtures_path: Path): + """ + Test that we can convert AggregateDataExtract to dictionary + """ + with open(fixtures_path / "nhgis_extract_v2.pkl", "rb") as infile: + extract = pickle.load(infile) + + # export extract to dict + dct = extract_to_dict(extract) + + assert dct["collection"] == "nhgis" + assert dct["datasets"] == { + '2010_SF1a': { + 'dataTables': ['P1'], + 'geogLevels': ['state'], + 'years': [], + 'breakdownValues': ['bs32.ge00'] + }, + '1990_STF1': { + 'dataTables': ['NP1', 'NP2'], + 'geogLevels': ['county'], + 'years': [], + 'breakdownValues': ['bs09.ge00'] + }, + } + assert dct["timeSeriesTables"] == { + "CW3": { + "geogLevels": ["state"], + "years": ["1990"] + } + } + assert dct["version"] == 2 + assert dct["shapefiles"] == ["us_state_1790_tl2000"] + assert dct["dataFormat"] == "csv_header" + assert dct["timeSeriesTableLayout"] == "time_by_column_layout" + + @pytest.mark.vcr def test_submit_extract_live(live_api_client: IpumsApiClient): """ @@ -812,6 +980,19 @@ def test_submit_extract_live(live_api_client: IpumsApiClient): assert live_api_client.extract_status(extract) == "queued" +@pytest.mark.vcr +def test_nhgis_submit_extract_live(live_api_client: IpumsApiClient, simple_nhgis_extract: AggregateDataExtract): + """ + Test that NHGIS extracts can be submitted + """ + extract = simple_nhgis_extract + + live_api_client.submit_extract(extract) + + assert extract._info["status"] == "queued" + assert extract.extract_id > 0 + + @pytest.mark.vcr def test_submit_extract_dict(live_api_client: IpumsApiClient): """ @@ -914,6 +1095,17 @@ def test_download_extract_r(live_api_client: IpumsApiClient, tmpdir: Path): assert (tmpdir / "usa_00196.R").exists() +@pytest.mark.vcr +def test_nhgis_download_extract(live_api_client: IpumsApiClient, tmpdir: Path,): + """ + Test that NHGIS extract files can be downloaded + """ + live_api_client.download_extract( + collection="nhgis", extract="1383", download_dir=tmpdir + ) + assert (tmpdir / "nhgis1383_csv.zip").exists() + + def test_define_extract_from_json(fixtures_path: Path): """Ensure extract can be created from json file""" extract = define_extract_from_json(fixtures_path / "example_extract_v2.json") @@ -1512,172 +1704,3 @@ def test_get_pages(live_api_client: IpumsApiClient): ) assert len(page1["data"]) == 5 -@pytest.fixture() -def simple_nhgis_extract(): - extract = AggregateDataExtract( - "nhgis", - description = "Simple extract for ipumspy unit testing", - datasets = [Dataset("1990_STF1", ["NP1"], ["state"])] - ) - - return extract - -@pytest.fixture() -def complex_nhgis_extract(): - extract = AggregateDataExtract( - "nhgis", - description = "Complex extract for ipumspy unit testing", - datasets = [ - Dataset("2010_SF1a", ["P1", "P2"], ["block"]), - Dataset("2010_SF2a", ["PCT1"], ["state"], breakdown_values = ["bs32.ge89", "bs33.ch002"]) - ], - time_series_tables = [ - TimeSeriesTable("CW3", ["nation", "state"], ["2000"]) - ], - shapefiles = ["us_state_1970_tl2000"], - data_format = "csv_header", - geographic_extents = ["010", "020"], - tst_layout = "time_by_row_layout", - breakdown_and_data_type_layout = "separate_files" - ) - - return extract - -def test_nhgis_build_extract(simple_nhgis_extract: AggregateDataExtract, complex_nhgis_extract: AggregateDataExtract): - assert complex_nhgis_extract.build() == { - "description": "Complex extract for ipumspy unit testing", - "dataFormat": "csv_header", - "collection": "nhgis", - "version": None, - "datasets": { - "2010_SF1a": { - "dataTables": ["P1", "P2"], - "geogLevels": ["block"], - "years": [], - "breakdownValues": [] - }, - "2010_SF2a": { - "dataTables": ["PCT1"], - "geogLevels": ["state"], - "years": [], - "breakdownValues": ["bs32.ge89", "bs33.ch002"] - } - }, - "breakdownAndDataTypeLayout": "separate_files", - "geographicExtents": ["010", "020"], - "timeSeriesTables": { - "CW3": { - "geogLevels": ["nation", "state"], - "years": ["2000"] - } - }, - "timeSeriesTableLayout": "time_by_row_layout", - "shapefiles": ["us_state_1970_tl2000"] - } - - assert simple_nhgis_extract.build() == { - "description": "Simple extract for ipumspy unit testing", - "dataFormat": "csv_no_header", - "collection": "nhgis", - "version": None, - "datasets": { - "1990_STF1": { - "dataTables": ["NP1"], - "geogLevels": ["state"], - "years": [], - "breakdownValues": [] - } - }, - "breakdownAndDataTypeLayout": "single_file", - "timeSeriesTables": {}, - "timeSeriesTableLayout": "time_by_column_layout", - "shapefiles": [] - } - -@pytest.mark.vcr -def test_agg_data_feature_errors(live_api_client: IpumsApiClient): - """ - Confirm that illegal Dataset and TimeSeriesTable objects throw appropriate errors - """ - extract = AggregateDataExtract( - "nhgis", - datasets = [Dataset("a", "b" ,"c", "d")], - time_series_tables = [TimeSeriesTable("a", "b")] - ) - - with pytest.raises(BadIpumsApiRequest) as exc_info: - live_api_client.submit_extract(extract) - - assert "The property '#/datasets/a/dataTables' of type string did not match the following type: array" in str(exc_info.value) - assert "The property '#/datasets/a/geogLevels' of type string did not match the following type: array" in str(exc_info.value) - assert "The property '#/datasets/a/years' of type string did not match the following type: array" in str(exc_info.value) - assert "The property '#/timeSeriesTables/A/geogLevels' of type string did not match the following type: array" in str(exc_info.value) - -@pytest.mark.vcr -def test_nhgis_submit_extract(live_api_client: IpumsApiClient, simple_nhgis_extract: AggregateDataExtract): - extract = simple_nhgis_extract - - live_api_client.submit_extract(extract) - - assert live_api_client.extract_status(extract) in ["started", "queued"] - assert extract.extract_id > 0 - -@pytest.mark.vcr -def test_nhgis_download_extract(live_api_client: IpumsApiClient, tmpdir: Path, ): - live_api_client.download_extract( - collection="nhgis", extract="1383", download_dir=tmpdir # TODO: generalize approach to avoid extract number? - ) - assert (tmpdir / "nhgis1383_csv.zip").exists() - -def test_nhgis_extract_from_dict(fixtures_path: Path): - with open(fixtures_path / "nhgis_extract_v2.yml") as infile: - extract = extract_from_dict(yaml.safe_load(infile)) - - assert isinstance(extract[0], AggregateDataExtract) - - for item in extract: - assert item.collection == "nhgis" - assert item.datasets == [ - Dataset(name = "1990_STF1", data_tables = ["NP1", "NP2"], geog_levels = ["county"]), - Dataset(name = "2010_SF1a", data_tables = ["P1"], geog_levels = ["state"]) - ] - assert item.time_series_tables == [ - TimeSeriesTable(name = "CW3", geog_levels = ["state"], years = ["1990"]) - ] - assert item.tst_layout == "time_by_column_layout" - assert item.shapefiles == [Shapefile(name = "us_state_1790_tl2000")] - assert item.data_format == "csv_header" - assert item.api_version == 2 - -def test_nhgis_extract_to_dict(fixtures_path: Path): - with open(fixtures_path / "nhgis_extract_v2.pkl", "rb") as infile: - extract = pickle.load(infile) - - # export extract to dict - dct = extract_to_dict(extract) - - assert dct["collection"] == "nhgis" - assert dct["datasets"] == { - '2010_SF1a': { - 'dataTables': ['P1'], - 'geogLevels': ['state'], - 'years': [], - 'breakdownValues': ['bs32.ge00'] - }, - '1990_STF1': { - 'dataTables': ['NP1', 'NP2'], - 'geogLevels': ['county'], - 'years': [], - 'breakdownValues': ['bs09.ge00'] - }, - } - assert dct["timeSeriesTables"] == { - "CW3": { - "geogLevels": ["state"], - "years": ["1990"] - } - } - assert dct["version"] == 2 - assert dct["shapefiles"] == ["us_state_1790_tl2000"] - assert dct["dataFormat"] == "csv_header" - assert dct["timeSeriesTableLayout"] == "time_by_column_layout" From 69197d96d15e685e1d56898f814bb67d6c5ff720 Mon Sep 17 00:00:00 2001 From: Finn Roberts Date: Tue, 15 Oct 2024 15:09:16 -0400 Subject: [PATCH 16/35] Fix type hinting --- src/ipumspy/api/extract.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ipumspy/api/extract.py b/src/ipumspy/api/extract.py index 38fba7c..75e4308 100644 --- a/src/ipumspy/api/extract.py +++ b/src/ipumspy/api/extract.py @@ -165,7 +165,7 @@ class TimeSeriesTable(IpumsObject): """IPUMS NHGIS time series table name/id""" geog_levels: List[str] # required parameter """Geographic level(s) at which to obtain data for this time series table""" - years: Optional[List[str]] = field(default_factory=list) + years: Optional[Union[List[str], List[int]]] = field(default_factory=list) """Years for which to obtain data for this time series table""" def __post_init__(self): @@ -639,8 +639,8 @@ class AggregateDataExtract(BaseExtract, collection_type="aggregate_data"): def __init__( self, collection: str, - datasets: Optional[Union[List[str], List[Dataset]]] = [], - time_series_tables: Optional[Union[List[str], List[TimeSeriesTable]]] = [], + datasets: Optional[List[Dataset]] = [], + time_series_tables: Optional[List[TimeSeriesTable]] = [], shapefiles: Optional[Union[List[str], List[Shapefile]]] = [], description: str = "", data_format: str = "csv_no_header", @@ -738,7 +738,7 @@ def build(self) -> Dict[str, Any]: built["shapefiles"] = [shapefile.name for shapefile in self.shapefiles] return built - + def extract_from_dict(dct: Dict[str, Any]) -> Union[BaseExtract, List[BaseExtract]]: """ From ddbb013d56f7225b59e4242aa0b30e63b1056fc5 Mon Sep 17 00:00:00 2001 From: Finn Roberts Date: Tue, 22 Oct 2024 11:53:51 -0400 Subject: [PATCH 17/35] Cast Dataset years argument to character --- src/ipumspy/api/extract.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/ipumspy/api/extract.py b/src/ipumspy/api/extract.py index 75e4308..6797c12 100644 --- a/src/ipumspy/api/extract.py +++ b/src/ipumspy/api/extract.py @@ -142,6 +142,9 @@ class Dataset(IpumsObject): breakdown_values: Optional[List[str]] = field(default_factory=list) """Breakdown values to apply to this dataset""" + def __post_init__(self): + self.years = [str(yr) for yr in self.years] + def build(self): built_dataset = self.__dict__.copy() # don't repeat the dataset name From 85edee3db273c685d3bb5369b3bea39dac547c3c Mon Sep 17 00:00:00 2001 From: Finn Roberts Date: Thu, 31 Oct 2024 10:02:39 -0400 Subject: [PATCH 18/35] Add metadata handlers --- src/ipumspy/api/__init__.py | 1 + src/ipumspy/api/core.py | 53 ++- src/ipumspy/api/extract.py | 18 +- src/ipumspy/api/metadata.py | 131 +++++++ .../test_metadata/test_get_metadata.yaml | 354 ++++++++++++++++++ .../test_get_metadata_catalog.yaml | 118 ++++++ tests/test_metadata.py | 64 ++++ 7 files changed, 728 insertions(+), 11 deletions(-) create mode 100644 src/ipumspy/api/metadata.py create mode 100644 tests/cassettes/test_metadata/test_get_metadata.yaml create mode 100644 tests/cassettes/test_metadata/test_get_metadata_catalog.yaml create mode 100644 tests/test_metadata.py diff --git a/src/ipumspy/api/__init__.py b/src/ipumspy/api/__init__.py index 8c9b6a1..6c760c2 100644 --- a/src/ipumspy/api/__init__.py +++ b/src/ipumspy/api/__init__.py @@ -5,3 +5,4 @@ from .core import IpumsApiClient from .exceptions import * from .extract import * +from .metadata import * diff --git a/src/ipumspy/api/core.py b/src/ipumspy/api/core.py index ed13e53..07cff3e 100644 --- a/src/ipumspy/api/core.py +++ b/src/ipumspy/api/core.py @@ -25,7 +25,13 @@ IpumsTimeoutException, TransientIpumsApiException, ) -from .extract import BaseExtract, MicrodataExtract, _get_collection_type, extract_from_dict +from .extract import ( + BaseExtract, + _get_collection_type, + extract_from_dict, + _camel_to_snake, +) +from .metadata import IpumsMetadata class ModifiedIpumsExtract(Warning): @@ -172,7 +178,7 @@ def submit_extract( # or in extract definition dict, assign it to the default if extract.api_version is None: extract.api_version = self.api_version - + response = self.post( f"{self.base_url}/extracts", params={"collection": extract.collection, "version": extract.api_version}, @@ -570,3 +576,46 @@ def get_all_sample_info(self, collection: str) -> Dict: for item in samples["data"]: samples_dict[item["name"]] = item["description"] return samples_dict + + def get_metadata_catalog( + self, collection: str, metadata_type: str, page_size: Optional[int] = 2500 + ) -> Generator[Dict, None, None]: + """ + Retrieve a catalog containing a summary of all resources of a given type for a given IPUMS collection + + Args: + collection: the name of the IPUMS collection to retrieve metadata for + metadata_type: name of the type of metadata to retrieve for this collection + page_size: The number of items to return per page. Default to maximum page size, 2500. + + Yields: + An iterator of metadata pages + """ + + yield from self._get_pages(collection, f"metadata/{metadata_type}", page_size) + + def get_metadata(self, obj: IpumsMetadata = None): + """ + Retrieve detailed metadata for a specific IPUMS resource + + Args: + obj: metadata object specifying the IPUMS resource for which to retrieve metadata + + Returns: + An object of the same class as ``obj`` with attributes containing the metadata receieved from the API + """ + + metadata_resp = self.get( + f"{self.base_url}/{obj._path}", + params={ + "collection": obj.collection, + "pageSize": 2500, + "version": self.api_version, + }, + ).json() + + metadata_resp = {_camel_to_snake(k): v for (k, v) in metadata_resp.items()} + metadata_class = IpumsMetadata._metadata_type[obj.metadata_type] + metadata = metadata_class(collection=obj.collection, **metadata_resp) + + return metadata diff --git a/src/ipumspy/api/extract.py b/src/ipumspy/api/extract.py index 6797c12..35d7907 100644 --- a/src/ipumspy/api/extract.py +++ b/src/ipumspy/api/extract.py @@ -281,6 +281,15 @@ def _get_collection_type(collection: str) -> str: return collection_types[collection] +def _camel_to_snake(key): + # don't mess with case for boolean values + if isinstance(key, bool): + return key + cap_idx = [0] + [key.index(i) for i in key if i.isupper()] + parts_list = [key[i:j].lower() for i, j in zip(cap_idx, cap_idx[1:] + [None])] + snake = "_".join(parts_list) + return snake + class BaseExtract: _collection_type_to_extract: Dict[(str, str), Type[BaseExtract]] = {} @@ -759,15 +768,6 @@ def extract_from_dict(dct: Dict[str, Any]) -> Union[BaseExtract, List[BaseExtrac # We are returning several extracts return [extract_from_dict(extract) for extract in dct["extracts"]] - def _camel_to_snake(key): - # don't mess with case for boolean values - if isinstance(key, bool): - return key - cap_idx = [0] + [key.index(i) for i in key if i.isupper()] - parts_list = [key[i:j].lower() for i, j in zip(cap_idx, cap_idx[1:] + [None])] - snake = "_".join(parts_list) - return snake - def _make_snake_ext(ext_dict): obj_keys = ["variables", "samples", "timeUseVariables", "datasets", "timeSeriesTables"] diff --git a/src/ipumspy/api/metadata.py b/src/ipumspy/api/metadata.py new file mode 100644 index 0000000..2893736 --- /dev/null +++ b/src/ipumspy/api/metadata.py @@ -0,0 +1,131 @@ +""" +Classes for requesting IPUMS metadata via the IPUMS API +""" + +from dataclasses import dataclass +from typing import Dict, List, Optional + + +@dataclass +class IpumsMetadata: + """ + Basic object to request and store metadata for an IPUMS resource + """ + + _metadata_type = {} + + def __init_subclass__(cls, metadata_type: str, **kwargs): + super().__init_subclass__(**kwargs) + cls.metadata_type = metadata_type + IpumsMetadata._metadata_type[metadata_type] = cls + + +@dataclass +class DatasetMetadata(IpumsMetadata, metadata_type="dataset"): + """ + Object to request and store metadata for an IPUMS dataset + """ + + collection: str + """name of an IPUMS data collection""" + name: str + """IPUMS NHGIS dataset name""" + description: Optional[str] = None + """description of the dataset""" + nhgis_id: Optional[str] = None + """NHGIS ID used in NHGIS files to reference the dataset""" + group: Optional[str] = None + """group of datasets to which the dataset belongs""" + sequence: Optional[str] = None + """order in which the dataset will appear in the metadata API and extracts""" + has_multiple_data_types: Optional[bool] = None + """ + logical indicating whether multiple data types exist for the dataset + (for example, ACS datasets include both estimates and margins of error) + """ + data_tables: Optional[List[Dict]] = None + """ + dictionary containing names, codes, and descriptions for all data tables + available for the dataset + """ + geog_levels: Optional[List[Dict]] = None + """ + dictionary containing names, descriptions, and extent information for the geographic + levels available for the dataset + """ + geographic_instances: Optional[List[Dict]] = None + """ + dictionary containing names and descriptions for all valid geographic extents + for the dataset + """ + breakdowns: Optional[List[Dict]] = None + """ + dictionary containing names, types, descriptions, and breakdown values for all breakdowns + available for the dataset. + """ + + def __post_init__(self): + self._path = f"metadata/datasets/{self.name}" + + +@dataclass +class TimeSeriesTableMetadata(IpumsMetadata, metadata_type="time_series_table"): + """ + Object to request and store metadata for an IPUMS time series table + """ + + collection: str + """name of an IPUMS data collection""" + name: str + """IPUMS NHGIS time series table name""" + description: Optional[str] = None + """description of the time series table""" + geographic_integration: Optional[str] = None + """ + The method by which the time series table aligns geographic units + across time. Nominal integration indicates that geographic units + are aligned by name (disregarding changes in unit boundaries). + Standardized integration indicates that data from multiple time + points are standardized to the indicated year's census units + """ + sequence: Optional[str] = None + """order in which the time series table will appear in the metadata API and extracts""" + time_series: Optional[List[Dict]] = None + """dictionary containing names and descriptions for the individual time series available for the time series table""" + years: Optional[List[Dict]] = None + """dictionary containing information on the available data years for the time series table""" + geog_levels: Optional[List[Dict]] = None + """dictionary containing names and descriptions for the geographic levels available for the time series table""" + + def __post_init__(self): + self._path = f"metadata/time_series_tables/{self.name}" + + +@dataclass +class DataTableMetadata(IpumsMetadata, metadata_type="data_table"): + """ + Object to request and store metadata for an IPUMS data table + """ + + collection: str + """name of an IPUMS data collection""" + name: str + """IPUMS data table name""" + dataset_name: str + """name of the dataset to which this data table belongs""" + description: Optional[str] = None + """description of the data table""" + universe: Optional[str] = None + """the statistical population measured by this data table""" + nhgis_code: Optional[str] = None + """ + the code identifying the data table in the extract. Variables in the extract + data will include column names prefixed with this code + """ + sequence: Optional[str] = None + """order in which the data table will appear in the metadata API and extracts""" + variables: Optional[List[Dict]] = None + """dictionary containing variable descriptions and codes for the variables included in the data table""" + + def __post_init__(self): + self._path = f"metadata/datasets/{self.dataset_name}/data_tables/{self.name}" diff --git a/tests/cassettes/test_metadata/test_get_metadata.yaml b/tests/cassettes/test_metadata/test_get_metadata.yaml new file mode 100644 index 0000000..4087bb2 --- /dev/null +++ b/tests/cassettes/test_metadata/test_get_metadata.yaml @@ -0,0 +1,354 @@ +interactions: +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/datasets/1990_STF1?collection=nhgis&pageSize=2500&version=2 + response: + body: + string: '{"name":"1990_STF1","nhgisId":"ds120","group":"1990 Census","description":"STF + 1 - 100% Data","sequence":4301,"hasMultipleDataTypes":false,"dataTables":[{"name":"NP1","description":"Persons","universe":"Persons","nhgisCode":"ET1","sequence":1,"datasetName":"1990_STF1","nVariables":1},{"name":"NP2","description":"Families","universe":"Families","nhgisCode":"EUD","sequence":2,"datasetName":"1990_STF1","nVariables":1},{"name":"NP3","description":"Households","universe":"Households","nhgisCode":"EUO","sequence":3,"datasetName":"1990_STF1","nVariables":1},{"name":"NP4","description":"Urban + and Rural","universe":"Persons","nhgisCode":"EUW","sequence":4,"datasetName":"1990_STF1","nVariables":4},{"name":"NP5","description":"Sex","universe":"Persons","nhgisCode":"EUX","sequence":5,"datasetName":"1990_STF1","nVariables":2},{"name":"NP6","description":"Race","universe":"Persons","nhgisCode":"EUY","sequence":6,"datasetName":"1990_STF1","nVariables":5},{"name":"NP7","description":"Race","universe":"Persons","nhgisCode":"EUZ","sequence":7,"datasetName":"1990_STF1","nVariables":25},{"name":"NP8","description":"Persons + of Hispanic Origin","universe":"Persons of Hispanic Origin","nhgisCode":"EU0","sequence":8,"datasetName":"1990_STF1","nVariables":1},{"name":"NP9","description":"Hispanic + Origin","universe":"Persons","nhgisCode":"EU1","sequence":9,"datasetName":"1990_STF1","nVariables":5},{"name":"NP10","description":"Hispanic + Origin by Race","universe":"Persons","nhgisCode":"ET2","sequence":10,"datasetName":"1990_STF1","nVariables":10},{"name":"NP11","description":"Age","universe":"Persons","nhgisCode":"ET3","sequence":11,"datasetName":"1990_STF1","nVariables":31},{"name":"NP12","description":"Race + by Sex by Age","universe":"Persons","nhgisCode":"ET4","sequence":12,"datasetName":"1990_STF1","nVariables":310},{"name":"NP13","description":"Sex + by Age","universe":"Persons of Hispanic Origin","nhgisCode":"ET5","sequence":13,"datasetName":"1990_STF1","nVariables":62},{"name":"NP14","description":"Sex + by Marital Status","universe":"Persons 15 Years and Over","nhgisCode":"ET6","sequence":14,"datasetName":"1990_STF1","nVariables":10},{"name":"NP15","description":"Household + Type and Relationship","universe":"Persons","nhgisCode":"ET7","sequence":15,"datasetName":"1990_STF1","nVariables":13},{"name":"NP16","description":"Household + Size and Household Type","universe":"Households","nhgisCode":"ET8","sequence":16,"datasetName":"1990_STF1","nVariables":10},{"name":"NP17","description":"Persons + in Families","universe":"Persons in families","nhgisCode":"ET9","sequence":17,"datasetName":"1990_STF1","nVariables":1},{"name":"NP17A","description":"Persons + per Family","universe":"Families","nhgisCode":"EUA","sequence":18,"datasetName":"1990_STF1","nVariables":1},{"name":"NP18","description":"Age + of Household Members by Household Type","universe":"Households","nhgisCode":"EUB","sequence":19,"datasetName":"1990_STF1","nVariables":10},{"name":"NP19","description":"Race + of Householder by Household Type","universe":"Households","nhgisCode":"EUC","sequence":20,"datasetName":"1990_STF1","nVariables":40},{"name":"NP20","description":"Household + Type","universe":"Households with Householder of Hispanic Origin","nhgisCode":"EUE","sequence":21,"datasetName":"1990_STF1","nVariables":8},{"name":"NP21","description":"Household + Type and Relationship","universe":"Persons Under 18 Years of Age","nhgisCode":"EUF","sequence":22,"datasetName":"1990_STF1","nVariables":9},{"name":"NP22","description":"Relationship + and Age","universe":"Persons Under 18 Years of Age","nhgisCode":"EUG","sequence":23,"datasetName":"1990_STF1","nVariables":37},{"name":"NP23","description":"Household + Type and Relationship","universe":"Persons 65 Years and Over","nhgisCode":"EUH","sequence":24,"datasetName":"1990_STF1","nVariables":12},{"name":"NP24","description":"Age + of Household Members by Household Size and Household Type","universe":"Households","nhgisCode":"EUI","sequence":25,"datasetName":"1990_STF1","nVariables":6},{"name":"NP25","description":"Age + of Household Members by Household Size and Household Type","universe":"Households","nhgisCode":"EUJ","sequence":26,"datasetName":"1990_STF1","nVariables":6},{"name":"NP26","description":"Household + Type","universe":"Households","nhgisCode":"EUK","sequence":27,"datasetName":"1990_STF1","nVariables":2},{"name":"NP27","description":"Household + Type and Household Size","universe":"Households","nhgisCode":"EUL","sequence":28,"datasetName":"1990_STF1","nVariables":13},{"name":"NP28","description":"Group + Quarters","universe":"Persons in group quarters","nhgisCode":"EUM","sequence":29,"datasetName":"1990_STF1","nVariables":10},{"name":"NP29","description":"Persons + Substituted","universe":"Persons","nhgisCode":"EUN","sequence":30,"datasetName":"1990_STF1","nVariables":3},{"name":"NP30","description":"Imputation + of Population Items","universe":"Persons not substituted","nhgisCode":"EUP","sequence":31,"datasetName":"1990_STF1","nVariables":2},{"name":"NP31","description":"Imputation + of Relationship","universe":"Persons not substituted","nhgisCode":"EUQ","sequence":32,"datasetName":"1990_STF1","nVariables":2},{"name":"NP32","description":"Imputation + of Sex","universe":"Persons not substituted","nhgisCode":"EUR","sequence":33,"datasetName":"1990_STF1","nVariables":2},{"name":"NP33","description":"Imputation + of Age","universe":"Persons not substituted","nhgisCode":"EUS","sequence":34,"datasetName":"1990_STF1","nVariables":2},{"name":"NP34","description":"Imputation + of Race","universe":"Persons not substituted","nhgisCode":"EUT","sequence":35,"datasetName":"1990_STF1","nVariables":2},{"name":"NP35","description":"Imputation + of Hispanic Origin","universe":"Persons not substituted","nhgisCode":"EUU","sequence":36,"datasetName":"1990_STF1","nVariables":2},{"name":"NP36","description":"Imputation + of Marital Status","universe":"Persons 15 Years and Over","nhgisCode":"EUV","sequence":37,"datasetName":"1990_STF1","nVariables":3},{"name":"NH1","description":"Housing + Units","universe":"Housing units","nhgisCode":"ESA","sequence":38,"datasetName":"1990_STF1","nVariables":1},{"name":"NH2","description":"Occupancy + Status","universe":"Housing units","nhgisCode":"ESN","sequence":39,"datasetName":"1990_STF1","nVariables":2},{"name":"NH3","description":"Tenure","universe":"Occupied + housing units","nhgisCode":"ES1","sequence":40,"datasetName":"1990_STF1","nVariables":2},{"name":"NH4","description":"Urban + and Rural","universe":"Housing units","nhgisCode":"ETF","sequence":41,"datasetName":"1990_STF1","nVariables":4},{"name":"NH5","description":"Vacancy + Status","universe":"Vacant housing units","nhgisCode":"ETQ","sequence":42,"datasetName":"1990_STF1","nVariables":6},{"name":"NH6","description":"Boarded-Up + Status","universe":"Vacant housing units","nhgisCode":"ETX","sequence":43,"datasetName":"1990_STF1","nVariables":2},{"name":"NH7","description":"Usual + Home Elsewhere","universe":"Vacant housing units","nhgisCode":"ETY","sequence":44,"datasetName":"1990_STF1","nVariables":2},{"name":"NH8","description":"Race + of Householder","universe":"Occupied housing units","nhgisCode":"ETZ","sequence":45,"datasetName":"1990_STF1","nVariables":5},{"name":"NH9","description":"Tenure + by Race of Householder","universe":"Occupied housing units","nhgisCode":"ET0","sequence":46,"datasetName":"1990_STF1","nVariables":10},{"name":"NH10","description":"Hispanic + Origin of Householder by Race of Householder","universe":"Occupied housing + units","nhgisCode":"ESB","sequence":47,"datasetName":"1990_STF1","nVariables":10},{"name":"NH11","description":"Tenure + by Race of Householder","universe":"Occupied Housing Units with Householder + of Hispanic Origin","nhgisCode":"ESC","sequence":48,"datasetName":"1990_STF1","nVariables":10},{"name":"NH12","description":"Tenure + by Age of Householder","universe":"Occupied housing units","nhgisCode":"ESD","sequence":49,"datasetName":"1990_STF1","nVariables":14},{"name":"NH13","description":"Rooms","universe":"Housing + units","nhgisCode":"ESE","sequence":50,"datasetName":"1990_STF1","nVariables":9},{"name":"NH14","description":"Aggregate + Rooms","universe":"Housing units","nhgisCode":"ESF","sequence":51,"datasetName":"1990_STF1","nVariables":1},{"name":"NH15","description":"Aggregate + Rooms by Tenure","universe":"Occupied housing units","nhgisCode":"ESG","sequence":52,"datasetName":"1990_STF1","nVariables":2},{"name":"NH16","description":"Aggregate + Rooms by Vacancy Status","universe":"Vacant housing units","nhgisCode":"ESH","sequence":53,"datasetName":"1990_STF1","nVariables":6},{"name":"NH17","description":"Persons + in Unit","universe":"Occupied housing units","nhgisCode":"ESI","sequence":54,"datasetName":"1990_STF1","nVariables":7},{"name":"NH17A","description":"Persons + per Occupied Housing Unit","universe":"Occupied housing units","nhgisCode":"ESJ","sequence":55,"datasetName":"1990_STF1","nVariables":1},{"name":"NH18","description":"Tenure + by Persons in Unit","universe":"Occupied housing units","nhgisCode":"ESK","sequence":56,"datasetName":"1990_STF1","nVariables":14},{"name":"NH18A","description":"Persons + per Occupied Housing Unit by Tenure","universe":"Occupied housing units","nhgisCode":"ESL","sequence":57,"datasetName":"1990_STF1","nVariables":2},{"name":"NH19","description":"Aggregate + Persons","universe":"Persons in occupied housing units","nhgisCode":"ESM","sequence":58,"datasetName":"1990_STF1","nVariables":1},{"name":"NH20","description":"Aggregate + Persons by Tenure","universe":"Persons in occupied housing units","nhgisCode":"ESO","sequence":59,"datasetName":"1990_STF1","nVariables":2},{"name":"NH21","description":"Persons + per Room","universe":"Occupied housing units","nhgisCode":"ESP","sequence":60,"datasetName":"1990_STF1","nVariables":5},{"name":"NH22","description":"Tenure + by Persons per Room","universe":"Occupied housing units","nhgisCode":"ESQ","sequence":61,"datasetName":"1990_STF1","nVariables":10},{"name":"NH23","description":"Value","universe":"Specified + owner-occupied housing units","nhgisCode":"ESR","sequence":62,"datasetName":"1990_STF1","nVariables":20},{"name":"NH23A","description":"Lower + Value Quartile","universe":"Specified owner-occupied housing units","nhgisCode":"ESS","sequence":63,"datasetName":"1990_STF1","nVariables":1},{"name":"NH23B","description":"Median + Value","universe":"Specified owner-occupied housing units","nhgisCode":"EST","sequence":64,"datasetName":"1990_STF1","nVariables":1},{"name":"NH23C","description":"Upper + Value Quartile","universe":"Specified owner-occupied housing units","nhgisCode":"ESU","sequence":65,"datasetName":"1990_STF1","nVariables":1},{"name":"NH24","description":"Aggregate + Value","universe":"Specified owner-occupied housing units","nhgisCode":"ESV","sequence":66,"datasetName":"1990_STF1","nVariables":1},{"name":"NH25","description":"Race + of Householder","universe":"Specified owner-occupied housing units","nhgisCode":"ESW","sequence":67,"datasetName":"1990_STF1","nVariables":5},{"name":"NH26","description":"Aggregate + Value by Race of Householder","universe":"Specified owner-occupied housing + units","nhgisCode":"ESX","sequence":68,"datasetName":"1990_STF1","nVariables":5},{"name":"NH27","description":"Hispanic + Origin of Householder","universe":"Specified owner-occupied housing units","nhgisCode":"ESY","sequence":69,"datasetName":"1990_STF1","nVariables":2},{"name":"NH28","description":"Aggregate + Value by Hispanic Origin of Householder","universe":"Specified owner-occupied + housing units","nhgisCode":"ESZ","sequence":70,"datasetName":"1990_STF1","nVariables":2},{"name":"NH29","description":"Aggregate + Value by Units in Structure","universe":"Owner-occupied housing units","nhgisCode":"ES0","sequence":71,"datasetName":"1990_STF1","nVariables":6},{"name":"NH30","description":"Vacancy + Status","universe":"Vacant housing units","nhgisCode":"ES2","sequence":72,"datasetName":"1990_STF1","nVariables":3},{"name":"NH31","description":"Aggregate + Price Asked","universe":"Specified Vacant-for-Sale Only Housing Units","nhgisCode":"ES3","sequence":73,"datasetName":"1990_STF1","nVariables":1},{"name":"NH32","description":"Contract + Rent","universe":"Specified renter-occupied housing units","nhgisCode":"ES4","sequence":74,"datasetName":"1990_STF1","nVariables":17},{"name":"NH32A","description":"Lower + Contract Rent Quartile","universe":"Specified renter-occupied housing units + paying cash rent","nhgisCode":"ES5","sequence":75,"datasetName":"1990_STF1","nVariables":1},{"name":"NH32B","description":"Median + Contract Rent","universe":"Specified renter-occupied housing units paying + cash rent","nhgisCode":"ES6","sequence":76,"datasetName":"1990_STF1","nVariables":1},{"name":"NH32C","description":"Upper + Contract Rent Quartile","universe":"Specified renter-occupied housing units + paying cash rent","nhgisCode":"ES7","sequence":77,"datasetName":"1990_STF1","nVariables":1},{"name":"NH33","description":"Aggregate + Contract Rent","universe":"Specified renter-occupied housing units paying + cash rent","nhgisCode":"ES8","sequence":78,"datasetName":"1990_STF1","nVariables":1},{"name":"NH34","description":"Race + of Householder","universe":"Specified renter-occupied housing units paying + cash rent","nhgisCode":"ES9","sequence":79,"datasetName":"1990_STF1","nVariables":5},{"name":"NH35","description":"Aggregate + Contract Rent by Race of Householder","universe":"Specified renter-occupied + housing units paying cash rent","nhgisCode":"ETA","sequence":80,"datasetName":"1990_STF1","nVariables":5},{"name":"NH36","description":"Hispanic + Origin of Householder","universe":"Specified renter-occupied housing units + paying cash rent","nhgisCode":"ETB","sequence":81,"datasetName":"1990_STF1","nVariables":2},{"name":"NH37","description":"Aggregate + Contract Rent by Hispanic Origin of Householder","universe":"Specified renter-occupied + housing units paying cash rent","nhgisCode":"ETC","sequence":82,"datasetName":"1990_STF1","nVariables":2},{"name":"NH38","description":"Aggregate + Rent Asked","universe":"Specified vacant-for-rent housing units","nhgisCode":"ETD","sequence":83,"datasetName":"1990_STF1","nVariables":1},{"name":"NH39","description":"Age + of Householder by Meals Included in Rent","universe":"Specified renter-occupied + housing units","nhgisCode":"ETE","sequence":84,"datasetName":"1990_STF1","nVariables":6},{"name":"NH40","description":"Vacancy + Status by Duration of Vacancy","universe":"Vacant housing units","nhgisCode":"ETG","sequence":85,"datasetName":"1990_STF1","nVariables":9},{"name":"NH41","description":"Units + in Structure","universe":"Housing units","nhgisCode":"ETH","sequence":86,"datasetName":"1990_STF1","nVariables":10},{"name":"NH42","description":"Units + in Structure","universe":"Vacant housing units","nhgisCode":"ETI","sequence":87,"datasetName":"1990_STF1","nVariables":10},{"name":"NH43","description":"Tenure + by Units in Structure","universe":"Occupied housing units","nhgisCode":"ETJ","sequence":88,"datasetName":"1990_STF1","nVariables":20},{"name":"NH44","description":"Aggregate + Persons by Tenure by Units in Structure","universe":"Persons in occupied housing + units","nhgisCode":"ETK","sequence":89,"datasetName":"1990_STF1","nVariables":20},{"name":"NH45","description":"Housing + Units Substituted","universe":"Housing units","nhgisCode":"ETL","sequence":90,"datasetName":"1990_STF1","nVariables":2},{"name":"NH46","description":"Imputation + of Housing Items","universe":"Housing Units Not Substituted","nhgisCode":"ETM","sequence":91,"datasetName":"1990_STF1","nVariables":2},{"name":"NH47","description":"Imputation + of Vacancy Status","universe":"Vacant housing units","nhgisCode":"ETN","sequence":92,"datasetName":"1990_STF1","nVariables":3},{"name":"NH48","description":"Imputation + of Duration of Vacancy","universe":"Vacant housing units","nhgisCode":"ETO","sequence":93,"datasetName":"1990_STF1","nVariables":3},{"name":"NH49","description":"Imputation + of Units in Structure","universe":"Housing Units Not Substituted","nhgisCode":"ETP","sequence":94,"datasetName":"1990_STF1","nVariables":2},{"name":"NH50","description":"Imputation + of Rooms","universe":"Housing Units Not Substituted","nhgisCode":"ETR","sequence":95,"datasetName":"1990_STF1","nVariables":2},{"name":"NH51","description":"Imputation + of Tenure","universe":"Occupied housing units","nhgisCode":"ETS","sequence":96,"datasetName":"1990_STF1","nVariables":3},{"name":"NH52","description":"Imputation + of Value","universe":"Specified owner-occupied housing units","nhgisCode":"ETT","sequence":97,"datasetName":"1990_STF1","nVariables":3},{"name":"NH53","description":"Imputation + of Price Asked","universe":"Specified Vacant-for-Sale Only Housing Units","nhgisCode":"ETU","sequence":98,"datasetName":"1990_STF1","nVariables":3},{"name":"NH54","description":"Imputation + of Contract Rent","universe":"Specified renter-occupied housing units","nhgisCode":"ETV","sequence":99,"datasetName":"1990_STF1","nVariables":4},{"name":"NH55","description":"Imputation + of Meals Included in Rent","universe":"Specified renter-occupied housing units","nhgisCode":"ETW","sequence":100,"datasetName":"1990_STF1","nVariables":4}],"geogLevels":[{"name":"nation","description":"Nation","hasGeogExtentSelection":false,"sequence":1},{"name":"region","description":"Region","hasGeogExtentSelection":false,"sequence":2},{"name":"division","description":"Division","hasGeogExtentSelection":false,"sequence":3},{"name":"state","description":"State","hasGeogExtentSelection":false,"sequence":4},{"name":"state_260","description":"American + Indian Area/Alaska Native Area/Hawaiian Home Land--State","hasGeogExtentSelection":false,"sequence":5},{"name":"state_262","description":"American + Indian Area/Alaska Native Area (Reservation or Statistical Entity Only)--State","hasGeogExtentSelection":false,"sequence":7},{"name":"state_264","description":"American + Indian Area (Off-Reservation Trust Land Only)/Hawaiian Home Land--State","hasGeogExtentSelection":false,"sequence":8},{"name":"state_381","description":"Metropolitan + Statistical Area/Consolidated Metropolitan Statistical Area--State","hasGeogExtentSelection":false,"sequence":13},{"name":"state_386","description":"Metropolitan + Statistical Area/Consolidated Metropolitan Statistical Area--Primary Metropolitan + Statistical Area--State","hasGeogExtentSelection":false,"sequence":14},{"name":"state_410","description":"Urban + Area--State","hasGeogExtentSelection":false,"sequence":22},{"name":"county","description":"State--County","hasGeogExtentSelection":false,"sequence":25},{"name":"county_155","description":"State--Place--County","hasGeogExtentSelection":false,"sequence":27},{"name":"county_510_103","description":"State--Congressional + District (1993-1995, 103rd Congress)--County","hasGeogExtentSelection":false,"sequence":36},{"name":"county_270","description":"American + Indian Area/Alaska Native Area/Hawaiian Home Land--State--County","hasGeogExtentSelection":false,"sequence":53},{"name":"county_272","description":"American + Indian Area/Alaska Native Area (Reservation or Statistical Entity Only)--State--County","hasGeogExtentSelection":false,"sequence":54},{"name":"county_274","description":"American + Indian Area (Off-Reservation Trust Land Only)/Hawaiian Home Land--State--County","hasGeogExtentSelection":false,"sequence":55},{"name":"county_383","description":"Metropolitan + Statistical Area/Consolidated Metropolitan Statistical Area--State--County","hasGeogExtentSelection":false,"sequence":58},{"name":"county_387","description":"Metropolitan + Statistical Area/Consolidated Metropolitan Statistical Area--Primary Metropolitan + Statistical Area--State--County","hasGeogExtentSelection":false,"sequence":59},{"name":"county_430","description":"Urban + Area--State--County","hasGeogExtentSelection":false,"sequence":63},{"name":"tract","description":"State--County--Census + Tract","hasGeogExtentSelection":false,"sequence":66},{"name":"tract_080","description":"State--County--County + Subdivision--Place/Remainder--Census Tract","hasGeogExtentSelection":false,"sequence":67},{"name":"blck_grp","description":"State--County--Census + Tract--Block Group","hasGeogExtentSelection":true,"sequence":85},{"name":"blck_grp_595_101","description":"State--County--County + Subdivision--Place/Remainder--Census Tract--Congressional District (1987-1993, + 100th-102nd Congress)--American Indian/Alaska Native Area/Remainder--Reservation/Trust + Lands/Remainder--Alaska Native Regional Corporation/Remainder--Block Group + [1990 partition, pre-Urban/Rural]","hasGeogExtentSelection":true,"sequence":89},{"name":"blck_grp_598_101","description":"State--County--County + Subdivision--Place/Remainder--Census Tract--Congressional District (1987-1993, + 100th-102nd Congress)--American Indian/Alaska Native Area/Remainder--Reservation/Trust + Lands/Remainder--Alaska Native Regional Corporation/Remainder--Urbanized Area/Remainder--Urban/Rural--Block + Group [1990 partition]","hasGeogExtentSelection":true,"sequence":90},{"name":"block","description":"State--County--Census + Tract--Block","hasGeogExtentSelection":true,"sequence":95},{"name":"cty_sub","description":"State--County--County + Subdivision","hasGeogExtentSelection":false,"sequence":102},{"name":"cty_sub_521_103","description":"State--Congressional + District (1993-1995, 103rd Congress)--County--County Subdivision [10000 or + more persons]","hasGeogExtentSelection":false,"sequence":113},{"name":"cty_sub_440","description":"Urban + Area--State--County--County Subdivision","hasGeogExtentSelection":false,"sequence":131},{"name":"place","description":"State--Place","hasGeogExtentSelection":false,"sequence":148},{"name":"place_070","description":"State--County--County + Subdivision--Place/Remainder","hasGeogExtentSelection":false,"sequence":155},{"name":"place_531_103","description":"State--Congressional + District (1993-1995, 103rd Congress)--Place [10000 or more persons]","hasGeogExtentSelection":false,"sequence":164},{"name":"place_382","description":"Metropolitan + Statistical Area/Consolidated Metropolitan Statistical Area--State--Central + City","hasGeogExtentSelection":false,"sequence":193},{"name":"place_341","description":"Metropolitan + Statistical Area/Consolidated Metropolitan Statistical Area--Primary Metropolitan + Statistical Area--State--Central City","hasGeogExtentSelection":false,"sequence":194},{"name":"place_460","description":"Urban + Area--State--Central Place","hasGeogExtentSelection":false,"sequence":197},{"name":"place_450","description":"Urban + Area--State--County--County Subdivision--Place/Remainder","hasGeogExtentSelection":false,"sequence":198},{"name":"c_city","description":"State--Consolidated + City","hasGeogExtentSelection":false,"sequence":201},{"name":"c_city_541_103","description":"State--Congressional + District (1993-1995, 103rd Congress)--Consolidated City [10000 or more persons]","hasGeogExtentSelection":false,"sequence":208},{"name":"cd103rd","description":"State--Congressional + District (1993-1995, 103rd Congress)","hasGeogExtentSelection":false,"sequence":221},{"name":"cd101st","description":"State--Congressional + District (1987-1993, 100th-102nd Congress)","hasGeogExtentSelection":false,"sequence":222},{"name":"anrc","description":"Alaska + Native Regional Corporation","hasGeogExtentSelection":false,"sequence":257},{"name":"anrc_560_103","description":"State--Congressional + District (1993-1995, 103rd Congress)--Alaska Native Regional Corporation","hasGeogExtentSelection":false,"sequence":265},{"name":"aianhh","description":"American + Indian Area/Alaska Native Area/Hawaiian Home Land","hasGeogExtentSelection":false,"sequence":268},{"name":"aianhh_550_103","description":"State--Congressional + District (1993-1995, 103rd Congress)--American Indian Area/Alaska Native Area/Hawaiian + Home Land","hasGeogExtentSelection":false,"sequence":282},{"name":"res_only","description":"American + Indian Area/Alaska Native Area (Reservation or Statistical Entity Only)","hasGeogExtentSelection":false,"sequence":285},{"name":"res_only_551_103","description":"State--Congressional + District (1993-1995, 103rd Congress)--American Indian Area/Alaska Native Area + (Reservation or Statistical Entity Only)","hasGeogExtentSelection":false,"sequence":292},{"name":"trust","description":"American + Indian Area (Off-Reservation Trust Land Only)/Hawaiian Home Land","hasGeogExtentSelection":false,"sequence":295},{"name":"trust_552_103","description":"State--Congressional + District (1993-1995, 103rd Congress)--American Indian Area (Off-Reservation + Trust Land Only)/Hawaiian Home Land","hasGeogExtentSelection":false,"sequence":302},{"name":"msa_cmsa","description":"Metropolitan + Statistical Area/Consolidated Metropolitan Statistical Area","hasGeogExtentSelection":false,"sequence":346},{"name":"pmsa_385","description":"Metropolitan + Statistical Area/Consolidated Metropolitan Statistical Area--Primary Metropolitan + Statistical Area","hasGeogExtentSelection":false,"sequence":354},{"name":"urb_area","description":"Urban + Area","hasGeogExtentSelection":false,"sequence":372}],"geographicInstances":[{"name":"010","description":"Alabama"},{"name":"020","description":"Alaska"},{"name":"040","description":"Arizona"},{"name":"050","description":"Arkansas"},{"name":"060","description":"California"},{"name":"080","description":"Colorado"},{"name":"090","description":"Connecticut"},{"name":"100","description":"Delaware"},{"name":"110","description":"District + of Columbia"},{"name":"120","description":"Florida"},{"name":"130","description":"Georgia"},{"name":"150","description":"Hawaii"},{"name":"160","description":"Idaho"},{"name":"170","description":"Illinois"},{"name":"180","description":"Indiana"},{"name":"190","description":"Iowa"},{"name":"200","description":"Kansas"},{"name":"210","description":"Kentucky"},{"name":"220","description":"Louisiana"},{"name":"230","description":"Maine"},{"name":"240","description":"Maryland"},{"name":"250","description":"Massachusetts"},{"name":"260","description":"Michigan"},{"name":"270","description":"Minnesota"},{"name":"280","description":"Mississippi"},{"name":"290","description":"Missouri"},{"name":"300","description":"Montana"},{"name":"310","description":"Nebraska"},{"name":"320","description":"Nevada"},{"name":"330","description":"New + Hampshire"},{"name":"340","description":"New Jersey"},{"name":"350","description":"New + Mexico"},{"name":"360","description":"New York"},{"name":"370","description":"North + Carolina"},{"name":"380","description":"North Dakota"},{"name":"390","description":"Ohio"},{"name":"400","description":"Oklahoma"},{"name":"410","description":"Oregon"},{"name":"420","description":"Pennsylvania"},{"name":"440","description":"Rhode + Island"},{"name":"450","description":"South Carolina"},{"name":"460","description":"South + Dakota"},{"name":"470","description":"Tennessee"},{"name":"480","description":"Texas"},{"name":"490","description":"Utah"},{"name":"500","description":"Vermont"},{"name":"510","description":"Virginia"},{"name":"530","description":"Washington"},{"name":"540","description":"West + Virginia"},{"name":"550","description":"Wisconsin"},{"name":"560","description":"Wyoming"}],"breakdowns":[{"name":"bs09","type":"Spatial","description":"Geographic + Subarea (1990 Census Population \u0026 Housing)","breakdownValues":[{"name":"bs09.ge00","description":"Total + area"},{"name":"bs09.ge01","description":"Urban"},{"name":"bs09.ge02","description":"Urban-in + urbanized area"},{"name":"bs09.ge03","description":"Urban-in urbanized area-in + urbanized area central"},{"name":"bs09.ge04","description":"Urban-in urbanized + area-not in urbanized area central place"},{"name":"bs09.ge05","description":"Urban-not + in urbanized area"},{"name":"bs09.ge06","description":"Urban-not in urbanized + area-place [10,000 or more persons]"},{"name":"bs09.ge07","description":"Urban-not + in urbanized area-place [2,500 to 9,999 persons]"},{"name":"bs09.ge08","description":"Rural"},{"name":"bs09.ge09","description":"Rural-place + [1,000 to 2,499 population, not in an extended city]"},{"name":"bs09.ge10","description":"Rural-place + [0 to 999 population, not in an extended city]"},{"name":"bs09.ge11","description":"Rural-not + in place [or rural part of extended city]"},{"name":"bs09.ge13","description":"Urban + portion of extended city"},{"name":"bs09.ge14","description":"Rural portion + of extended city"},{"name":"bs09.ge20","description":"In metropolitan statistical + area/consolidated metropolitan statistical area"},{"name":"bs09.ge21","description":"In + metropolitan statistical area/consolidated metropolitan statistical area-urban"},{"name":"bs09.ge22","description":"In + metropolitan statistical area/consolidated metropolitan statistical area-rural"},{"name":"bs09.ge23","description":"In + metropolitan statistical area/consolidated metropolitan statistical area-in + metropolitan statistical area/primary metropolitan statistical area central + city"},{"name":"bs09.ge24","description":"In metropolitan statistical area/consolidated + metropolitan statistical area-not in metropolitan statistical area/primary + metropolitan statistical area central city"},{"name":"bs09.ge25","description":"In + metropolitan statistical area/consolidated metropolitan statistical area-not + in metropolitan statistical area/primary metropolitan statistical area central + city-urban"},{"name":"bs09.ge26","description":"In metropolitan statistical + area/consolidated metropolitan statistical area-not in metropolitan statistical + area/primary metropolitan statistical area central city-urban-in urbanized + area"},{"name":"bs09.ge27","description":"In metropolitan statistical area/consolidated + metropolitan statistical area-not in metropolitan statistical area/primary + metropolitan statistical area central city-urban-not in urbanized area"},{"name":"bs09.ge28","description":"In + metropolitan statistical area/consolidated metropolitan statistical area-not + in metropolitan statistical area/primary metropolitan statistical area central + city-rural"},{"name":"bs09.ge30","description":"Not in metropolitan statistical + area/consolidated metropolitan statistical area"},{"name":"bs09.ge31","description":"Not + in metropolitan statistical area/consolidated metropolitan statistical area-urban"},{"name":"bs09.ge32","description":"Not + in metropolitan statistical area/consolidated metropolitan statistical area-urban-in + urbanized area"},{"name":"bs09.ge33","description":"Not in metropolitan statistical + area/consolidated metropolitan statistical area-urban-not in urbanized area"},{"name":"bs09.ge34","description":"Not + in metropolitan statistical area/consolidated metropolitan statistical area-urban-not + in urbanized area-place [10,000 or more population]"},{"name":"bs09.ge35","description":"Not + in metropolitan statistical area/consolidated metropolitan statistical area-urban-not + in urbanized area-place [2,500 - 9,999 population]"},{"name":"bs09.ge36","description":"Not + in metropolitan statistical area/consolidated metropolitan statistical area-rural"},{"name":"bs09.ge40","description":"American + Indian reservation and trust land [American Indian reservations (AIR codes + 0001-4989) including any trust lands]"},{"name":"bs09.ge42","description":"Tribal + Jurisdiction Statistical Area [Oklahoma only]"},{"name":"bs09.ge43","description":"Tribal + Designated Statistical Area"},{"name":"bs09.ge44","description":"Alaska Native + village statistical area [Alaska only]"}]}]}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '31294' + Content-Type: + - application/json; charset=utf-8 + Date: + - Mon, 11 Nov 2024 20:52:11 GMT + Etag: + - W/"1d5c2dc8d7bfe618e8c7f9991a0dc4ce" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - 0931b24f-99f6-4ca3-a532-b48eaa411bdc + X-Runtime: + - '0.012075' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/datasets/2017_2021_ACS5a/data_tables/B01001?collection=nhgis&pageSize=2500&version=2 + response: + body: + string: '{"name":"B01001","description":"Sex by Age","universe":"Total population","nhgisCode":"AONT","sequence":1,"datasetName":"2017_2021_ACS5a","variables":[{"description":"Total","nhgisCode":"AONT001"},{"description":"Male","nhgisCode":"AONT002"},{"description":"Male: + Under 5 years","nhgisCode":"AONT003"},{"description":"Male: 5 to 9 years","nhgisCode":"AONT004"},{"description":"Male: + 10 to 14 years","nhgisCode":"AONT005"},{"description":"Male: 15 to 17 years","nhgisCode":"AONT006"},{"description":"Male: + 18 and 19 years","nhgisCode":"AONT007"},{"description":"Male: 20 years","nhgisCode":"AONT008"},{"description":"Male: + 21 years","nhgisCode":"AONT009"},{"description":"Male: 22 to 24 years","nhgisCode":"AONT010"},{"description":"Male: + 25 to 29 years","nhgisCode":"AONT011"},{"description":"Male: 30 to 34 years","nhgisCode":"AONT012"},{"description":"Male: + 35 to 39 years","nhgisCode":"AONT013"},{"description":"Male: 40 to 44 years","nhgisCode":"AONT014"},{"description":"Male: + 45 to 49 years","nhgisCode":"AONT015"},{"description":"Male: 50 to 54 years","nhgisCode":"AONT016"},{"description":"Male: + 55 to 59 years","nhgisCode":"AONT017"},{"description":"Male: 60 and 61 years","nhgisCode":"AONT018"},{"description":"Male: + 62 to 64 years","nhgisCode":"AONT019"},{"description":"Male: 65 and 66 years","nhgisCode":"AONT020"},{"description":"Male: + 67 to 69 years","nhgisCode":"AONT021"},{"description":"Male: 70 to 74 years","nhgisCode":"AONT022"},{"description":"Male: + 75 to 79 years","nhgisCode":"AONT023"},{"description":"Male: 80 to 84 years","nhgisCode":"AONT024"},{"description":"Male: + 85 years and over","nhgisCode":"AONT025"},{"description":"Female","nhgisCode":"AONT026"},{"description":"Female: + Under 5 years","nhgisCode":"AONT027"},{"description":"Female: 5 to 9 years","nhgisCode":"AONT028"},{"description":"Female: + 10 to 14 years","nhgisCode":"AONT029"},{"description":"Female: 15 to 17 years","nhgisCode":"AONT030"},{"description":"Female: + 18 and 19 years","nhgisCode":"AONT031"},{"description":"Female: 20 years","nhgisCode":"AONT032"},{"description":"Female: + 21 years","nhgisCode":"AONT033"},{"description":"Female: 22 to 24 years","nhgisCode":"AONT034"},{"description":"Female: + 25 to 29 years","nhgisCode":"AONT035"},{"description":"Female: 30 to 34 years","nhgisCode":"AONT036"},{"description":"Female: + 35 to 39 years","nhgisCode":"AONT037"},{"description":"Female: 40 to 44 years","nhgisCode":"AONT038"},{"description":"Female: + 45 to 49 years","nhgisCode":"AONT039"},{"description":"Female: 50 to 54 years","nhgisCode":"AONT040"},{"description":"Female: + 55 to 59 years","nhgisCode":"AONT041"},{"description":"Female: 60 and 61 years","nhgisCode":"AONT042"},{"description":"Female: + 62 to 64 years","nhgisCode":"AONT043"},{"description":"Female: 65 and 66 years","nhgisCode":"AONT044"},{"description":"Female: + 67 to 69 years","nhgisCode":"AONT045"},{"description":"Female: 70 to 74 years","nhgisCode":"AONT046"},{"description":"Female: + 75 to 79 years","nhgisCode":"AONT047"},{"description":"Female: 80 to 84 years","nhgisCode":"AONT048"},{"description":"Female: + 85 years and over","nhgisCode":"AONT049"}]}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '3124' + Content-Type: + - application/json; charset=utf-8 + Date: + - Mon, 11 Nov 2024 20:52:11 GMT + Etag: + - W/"cca984aa65af831c2d9b54dd5aa8c148" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - 8a5e8815-5a51-492a-855a-69a659cdaf5d + X-Runtime: + - '0.010915' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_metadata/test_get_metadata_catalog.yaml b/tests/cassettes/test_metadata/test_get_metadata_catalog.yaml new file mode 100644 index 0000000..9224938 --- /dev/null +++ b/tests/cassettes/test_metadata/test_get_metadata_catalog.yaml @@ -0,0 +1,118 @@ +interactions: +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/datasets?collection=nhgis&version=2&pageSize=5 + response: + body: + string: '{"data":[{"name":"1790_cPop","group":"1790 Census","description":"Population + Data [US, States \u0026 Counties]","sequence":101},{"name":"1800_cPop","group":"1800 + Census","description":"Population Data [US, States \u0026 Counties]","sequence":201},{"name":"1810_cPop","group":"1810 + Census","description":"Population Data [US, States \u0026 Counties]","sequence":301},{"name":"1820_cPop","group":"1820 + Census","description":"Population Data [US, States \u0026 Counties]","sequence":401},{"name":"1830_cPop","group":"1830 + Census","description":"Population Data [US, States \u0026 Counties]","sequence":501}],"pageNumber":1,"pageSize":5,"totalCount":261,"links":{"previousPage":null,"nextPage":"https://api.ipums.org/metadata/datasets?collection=nhgis\u0026pageNumber=2\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '799' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 08 Nov 2024 20:58:05 GMT + Etag: + - W/"c7e3011132a3184ed0b17d1f992a9334" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - 6308a60b-5d61-4e24-8bde-09146b0bf72e + X-Runtime: + - '0.011387' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/samples?collection=usa&version=2&pageSize=5 + response: + body: + string: '{"data":[{"name":"us1850a","description":"1850 1%"},{"name":"us1850c","description":"1850 + 100% sample (Revised November 2023)"},{"name":"us1860a","description":"1860 + 1%"},{"name":"us1860b","description":"1860 1% sample with black oversample"},{"name":"us1860c","description":"1860 + 100% sample (Revised November 2023)"}],"pageNumber":1,"pageSize":5,"totalCount":146,"links":{"previousPage":null,"nextPage":"https://api.ipums.org/metadata/samples?collection=usa\u0026pageNumber=2\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '511' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 08 Nov 2024 20:58:05 GMT + Etag: + - W/"407985790e783a8f7798ff151b8e4acc" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - bcf284b7-8e5e-4f92-a784-ec8e49426a02 + X-Runtime: + - '0.113665' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +version: 1 diff --git a/tests/test_metadata.py b/tests/test_metadata.py new file mode 100644 index 0000000..bb57f88 --- /dev/null +++ b/tests/test_metadata.py @@ -0,0 +1,64 @@ +import os +import pytest + +from ipumspy.api import IpumsApiClient + +from ipumspy.api.metadata import ( + DatasetMetadata, + DataTableMetadata, + TimeSeriesTableMetadata, +) + + +@pytest.fixture(scope="function") +def live_api_client(environment_variables) -> IpumsApiClient: + live_client = IpumsApiClient(os.environ.get("IPUMS_API_KEY")) + return live_client + + +@pytest.mark.vcr +def test_get_metadata_catalog(live_api_client: IpumsApiClient): + """ + Test that we can retrieve paginated metadata endpoints from API + """ + + datasets = live_api_client.get_metadata_catalog("nhgis", "datasets", page_size=5) + samples = live_api_client.get_metadata_catalog("usa", "samples", page_size=5) + + ds = next(datasets)["data"] + samp = next(samples)["data"] + + assert [d["name"] for d in ds] == [ + "1790_cPop", + "1800_cPop", + "1810_cPop", + "1820_cPop", + "1830_cPop", + ] + assert [s["name"] for s in samp] == [ + "us1850a", + "us1850c", + "us1860a", + "us1860b", + "us1860c", + ] + assert list(ds[0].keys()) == ["name", "group", "description", "sequence"] + assert list(samp[0].keys()) == ["name", "description"] + + +@pytest.mark.vcr +def test_get_metadata(live_api_client: IpumsApiClient): + ds = DatasetMetadata("nhgis", "1990_STF1") + dt = DataTableMetadata("nhgis", "B01001", "2017_2021_ACS5a") + + ds = live_api_client.get_metadata(ds) + dt = live_api_client.get_metadata(dt) + + assert isinstance(ds, DatasetMetadata) + assert isinstance(dt, DataTableMetadata) + + assert ds.description == "STF 1 - 100% Data" + assert dt.description == "Sex by Age" + + assert len(ds.data_tables) == 100 + assert len(dt.variables) == 49 From bf68ce43917ec22934ea6e20079642b799cb3545 Mon Sep 17 00:00:00 2001 From: Finn Roberts Date: Fri, 1 Nov 2024 09:30:27 -0400 Subject: [PATCH 19/35] Fix bugs in _camel_to_snake Did not split correctly when string contained duplicate uppercase characters in different parts of string --- src/ipumspy/api/extract.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/ipumspy/api/extract.py b/src/ipumspy/api/extract.py index 35d7907..324b9e0 100644 --- a/src/ipumspy/api/extract.py +++ b/src/ipumspy/api/extract.py @@ -281,11 +281,14 @@ def _get_collection_type(collection: str) -> str: return collection_types[collection] + def _camel_to_snake(key): # don't mess with case for boolean values if isinstance(key, bool): return key - cap_idx = [0] + [key.index(i) for i in key if i.isupper()] + cap_idx = [0] + [ + key.index(l, i) for i, l in enumerate(key) if l.isupper() and i != 0 + ] parts_list = [key[i:j].lower() for i, j in zip(cap_idx, cap_idx[1:] + [None])] snake = "_".join(parts_list) return snake From 7ef31cdd6621aeab65d961d75296f3e86bd99d2b Mon Sep 17 00:00:00 2001 From: Finn Roberts Date: Mon, 11 Nov 2024 16:17:15 -0500 Subject: [PATCH 20/35] black --- src/ipumspy/api/extract.py | 12 +++- tests/test_api.py | 124 +++++++++++++++++++++---------------- 2 files changed, 81 insertions(+), 55 deletions(-) diff --git a/src/ipumspy/api/extract.py b/src/ipumspy/api/extract.py index 324b9e0..17098f7 100644 --- a/src/ipumspy/api/extract.py +++ b/src/ipumspy/api/extract.py @@ -707,7 +707,7 @@ def __init__( self.geographic_extents = geographic_extents self.breakdown_and_data_type_layout = breakdown_and_data_type_layout self.tst_layout = tst_layout - + self.api_version = ( self.extract_api_version(kwargs) if len(kwargs.keys()) > 0 @@ -753,7 +753,7 @@ def build(self) -> Dict[str, Any]: built["shapefiles"] = [shapefile.name for shapefile in self.shapefiles] return built - + def extract_from_dict(dct: Dict[str, Any]) -> Union[BaseExtract, List[BaseExtract]]: """ @@ -772,7 +772,13 @@ def extract_from_dict(dct: Dict[str, Any]) -> Union[BaseExtract, List[BaseExtrac return [extract_from_dict(extract) for extract in dct["extracts"]] def _make_snake_ext(ext_dict): - obj_keys = ["variables", "samples", "timeUseVariables", "datasets", "timeSeriesTables"] + obj_keys = [ + "variables", + "samples", + "timeUseVariables", + "datasets", + "timeSeriesTables", + ] for key in ext_dict.keys(): if isinstance(ext_dict[key], dict): diff --git a/tests/test_api.py b/tests/test_api.py index 5a7c8f9..d08d5ab 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -73,29 +73,33 @@ def live_api_client(environment_variables) -> IpumsApiClient: def simple_nhgis_extract() -> AggregateDataExtract: extract = AggregateDataExtract( "nhgis", - description = "Simple extract for ipumspy unit testing", - datasets = [Dataset("1990_STF1", ["NP1"], ["state"])] + description="Simple extract for ipumspy unit testing", + datasets=[Dataset("1990_STF1", ["NP1"], ["state"])], ) return extract + @pytest.fixture() def complex_nhgis_extract() -> AggregateDataExtract: extract = AggregateDataExtract( "nhgis", - description = "Complex extract for ipumspy unit testing", - datasets = [ + description="Complex extract for ipumspy unit testing", + datasets=[ Dataset("2010_SF1a", ["P1", "P2"], ["block"]), - Dataset("2010_SF2a", ["PCT1"], ["state"], breakdown_values = ["bs32.ge89", "bs33.ch002"]) - ], - time_series_tables = [ - TimeSeriesTable("CW3", ["nation", "state"], ["2000"]) + Dataset( + "2010_SF2a", + ["PCT1"], + ["state"], + breakdown_values=["bs32.ge89", "bs33.ch002"], + ), ], - shapefiles = ["us_state_1970_tl2000"], - data_format = "csv_header", - geographic_extents = ["010", "020"], - tst_layout = "time_by_row_layout", - breakdown_and_data_type_layout = "separate_files" + time_series_tables=[TimeSeriesTable("CW3", ["nation", "state"], ["2000"])], + shapefiles=["us_state_1970_tl2000"], + data_format="csv_header", + geographic_extents=["010", "020"], + tst_layout="time_by_row_layout", + breakdown_and_data_type_layout="separate_files", ) return extract @@ -385,17 +389,29 @@ def test_nhgis_feature_errors(live_api_client: IpumsApiClient): """ extract = AggregateDataExtract( "nhgis", - datasets = [Dataset("a", "b" ,"c", "d")], - time_series_tables = [TimeSeriesTable("a", "b")] + datasets=[Dataset("a", "b", "c", "d")], + time_series_tables=[TimeSeriesTable("a", "b")], ) with pytest.raises(BadIpumsApiRequest) as exc_info: live_api_client.submit_extract(extract) - assert "The property '#/datasets/a/dataTables' of type string did not match the following type: array" in str(exc_info.value) - assert "The property '#/datasets/a/geogLevels' of type string did not match the following type: array" in str(exc_info.value) - assert "The property '#/datasets/a/years' of type string did not match the following type: array" in str(exc_info.value) - assert "The property '#/timeSeriesTables/A/geogLevels' of type string did not match the following type: array" in str(exc_info.value) + assert ( + "The property '#/datasets/a/dataTables' of type string did not match the following type: array" + in str(exc_info.value) + ) + assert ( + "The property '#/datasets/a/geogLevels' of type string did not match the following type: array" + in str(exc_info.value) + ) + assert ( + "The property '#/datasets/a/years' of type string did not match the following type: array" + in str(exc_info.value) + ) + assert ( + "The property '#/timeSeriesTables/A/geogLevels' of type string did not match the following type: array" + in str(exc_info.value) + ) def test_cps_build_extract(): @@ -593,7 +609,10 @@ def test_atus_build_extract(): ) -def test_nhgis_build_extract(simple_nhgis_extract: AggregateDataExtract, complex_nhgis_extract: AggregateDataExtract): +def test_nhgis_build_extract( + simple_nhgis_extract: AggregateDataExtract, + complex_nhgis_extract: AggregateDataExtract, +): """ Test NHGIS extract build structure """ @@ -607,25 +626,22 @@ def test_nhgis_build_extract(simple_nhgis_extract: AggregateDataExtract, complex "dataTables": ["P1", "P2"], "geogLevels": ["block"], "years": [], - "breakdownValues": [] + "breakdownValues": [], }, "2010_SF2a": { "dataTables": ["PCT1"], "geogLevels": ["state"], "years": [], - "breakdownValues": ["bs32.ge89", "bs33.ch002"] - } + "breakdownValues": ["bs32.ge89", "bs33.ch002"], + }, }, "breakdownAndDataTypeLayout": "separate_files", "geographicExtents": ["010", "020"], "timeSeriesTables": { - "CW3": { - "geogLevels": ["nation", "state"], - "years": ["2000"] - } + "CW3": {"geogLevels": ["nation", "state"], "years": ["2000"]} }, "timeSeriesTableLayout": "time_by_row_layout", - "shapefiles": ["us_state_1970_tl2000"] + "shapefiles": ["us_state_1970_tl2000"], } assert simple_nhgis_extract.build() == { @@ -638,13 +654,13 @@ def test_nhgis_build_extract(simple_nhgis_extract: AggregateDataExtract, complex "dataTables": ["NP1"], "geogLevels": ["state"], "years": [], - "breakdownValues": [] + "breakdownValues": [], } }, "breakdownAndDataTypeLayout": "single_file", "timeSeriesTables": {}, "timeSeriesTableLayout": "time_by_column_layout", - "shapefiles": [] + "shapefiles": [], } @@ -893,14 +909,16 @@ def test_nhgis_extract_from_dict(fixtures_path: Path): for item in extract: assert item.collection == "nhgis" assert item.datasets == [ - Dataset(name = "1990_STF1", data_tables = ["NP1", "NP2"], geog_levels = ["county"]), - Dataset(name = "2010_SF1a", data_tables = ["P1"], geog_levels = ["state"]) + Dataset( + name="1990_STF1", data_tables=["NP1", "NP2"], geog_levels=["county"] + ), + Dataset(name="2010_SF1a", data_tables=["P1"], geog_levels=["state"]), ] assert item.time_series_tables == [ - TimeSeriesTable(name = "CW3", geog_levels = ["state"], years = ["1990"]) + TimeSeriesTable(name="CW3", geog_levels=["state"], years=["1990"]) ] assert item.tst_layout == "time_by_column_layout" - assert item.shapefiles == [Shapefile(name = "us_state_1790_tl2000")] + assert item.shapefiles == [Shapefile(name="us_state_1790_tl2000")] assert item.data_format == "csv_header" assert item.api_version == 2 @@ -940,24 +958,21 @@ def test_nhgis_extract_to_dict(fixtures_path: Path): assert dct["collection"] == "nhgis" assert dct["datasets"] == { - '2010_SF1a': { - 'dataTables': ['P1'], - 'geogLevels': ['state'], - 'years': [], - 'breakdownValues': ['bs32.ge00'] + "2010_SF1a": { + "dataTables": ["P1"], + "geogLevels": ["state"], + "years": [], + "breakdownValues": ["bs32.ge00"], }, - '1990_STF1': { - 'dataTables': ['NP1', 'NP2'], - 'geogLevels': ['county'], - 'years': [], - 'breakdownValues': ['bs09.ge00'] + "1990_STF1": { + "dataTables": ["NP1", "NP2"], + "geogLevels": ["county"], + "years": [], + "breakdownValues": ["bs09.ge00"], }, } assert dct["timeSeriesTables"] == { - "CW3": { - "geogLevels": ["state"], - "years": ["1990"] - } + "CW3": {"geogLevels": ["state"], "years": ["1990"]} } assert dct["version"] == 2 assert dct["shapefiles"] == ["us_state_1790_tl2000"] @@ -981,7 +996,9 @@ def test_submit_extract_live(live_api_client: IpumsApiClient): @pytest.mark.vcr -def test_nhgis_submit_extract_live(live_api_client: IpumsApiClient, simple_nhgis_extract: AggregateDataExtract): +def test_nhgis_submit_extract_live( + live_api_client: IpumsApiClient, simple_nhgis_extract: AggregateDataExtract +): """ Test that NHGIS extracts can be submitted """ @@ -1003,7 +1020,7 @@ def test_submit_extract_dict(live_api_client: IpumsApiClient): "version": 2, "samples": {"us2017a": {}}, "variables": {"AGE": {}}, - "data_structure": {"hierarchical": {}} + "data_structure": {"hierarchical": {}}, } extract = live_api_client.submit_extract(extract_dict) @@ -1013,6 +1030,7 @@ def test_submit_extract_dict(live_api_client: IpumsApiClient): assert Sample("us2017a") in extract.samples assert extract_dict["data_structure"] == extract.data_structure + @pytest.mark.vcr def test_submit_hierarchical_extract_live(live_api_client: IpumsApiClient): """ @@ -1096,7 +1114,10 @@ def test_download_extract_r(live_api_client: IpumsApiClient, tmpdir: Path): @pytest.mark.vcr -def test_nhgis_download_extract(live_api_client: IpumsApiClient, tmpdir: Path,): +def test_nhgis_download_extract( + live_api_client: IpumsApiClient, + tmpdir: Path, +): """ Test that NHGIS extract files can be downloaded """ @@ -1703,4 +1724,3 @@ def test_get_pages(live_api_client: IpumsApiClient): live_api_client._get_pages(collection="usa", endpoint="extracts", page_size=5) ) assert len(page1["data"]) == 5 - From ebddba732d97296a95f90b61890f4f0219d0df8c Mon Sep 17 00:00:00 2001 From: Finn Roberts Date: Mon, 11 Nov 2024 16:18:13 -0500 Subject: [PATCH 21/35] Fix `initial_wait_time` arg typo --- src/ipumspy/cli.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ipumspy/cli.py b/src/ipumspy/cli.py index f4c5a2a..528f9e5 100644 --- a/src/ipumspy/cli.py +++ b/src/ipumspy/cli.py @@ -225,7 +225,7 @@ def submit_and_download_command( api_key: str, output_dir: Optional[str], num_retries: int, - inital_wait_time: float, + initial_wait_time: float, max_wait_time: float, timeout: Optional[float], ): @@ -252,7 +252,7 @@ def submit_and_download_command( try: api_client.wait_for_extract( ext, - inital_wait_time=float(inital_wait_time), + inital_wait_time=float(initial_wait_time), max_wait_time=float(max_wait_time), timeout=timeout if timeout is not None else float(timeout), ) From 12267f7a5466d1989d64e85a6257ffc8b2aeae04 Mon Sep 17 00:00:00 2001 From: Finn Roberts Date: Wed, 13 Nov 2024 15:52:34 -0500 Subject: [PATCH 22/35] Doc and website updates --- docs/source/cli.rst | 22 +- docs/source/extracts.rst | 474 ------------------ docs/source/getting_started.rst | 130 +++-- docs/source/index.rst | 26 +- docs/source/ipums_api/index.rst | 396 +++++++++++++++ .../ipums_api/ipums_api_aggregate/index.rst | 274 ++++++++++ .../ipums_api/ipums_api_micro/index.rst | 402 +++++++++++++++ docs/source/reading_data.rst | 123 ++++- docs/source/reference/api.rst | 19 + docs/source/variables.rst | 66 --- src/ipumspy/api/core.py | 28 +- src/ipumspy/api/extract.py | 90 +++- src/ipumspy/api/metadata.py | 28 +- src/ipumspy/ddi.py | 2 +- 14 files changed, 1393 insertions(+), 687 deletions(-) delete mode 100644 docs/source/extracts.rst create mode 100644 docs/source/ipums_api/index.rst create mode 100644 docs/source/ipums_api/ipums_api_aggregate/index.rst create mode 100644 docs/source/ipums_api/ipums_api_micro/index.rst delete mode 100644 docs/source/variables.rst diff --git a/docs/source/cli.rst b/docs/source/cli.rst index 83c792f..e9e1249 100644 --- a/docs/source/cli.rst +++ b/docs/source/cli.rst @@ -7,7 +7,7 @@ Command Line Interface ====================== -``ipumspy`` allows you to interact with the IPUMS API via the command line. If you +ipumspy allows you to interact with the IPUMS API via the command line. If you have :doc:`installed ` with ``pip``, then you should have an ``ipums`` command available on the command line. @@ -17,14 +17,13 @@ You can explore what commands are available by running the ``--help`` option: ipums --help -In particular, suppose that you have specified an extract in an ``ipums.yml`` file -as described in the :doc:`getting started guide `. +In particular, suppose that you have specified an extract in an ``ipums.yaml`` file +as described in the :ref:`IPUMS API introduction `. .. code:: yaml description: Simple IPUMS extract collection: usa - api_version: 2 samples: - us2012b variables: @@ -35,7 +34,7 @@ Then you can submit, wait for, and download the extract in a single line: .. code:: bash - ipums submit-and-download -k ipums.yml + ipums submit-and-download -k ipums.yaml Much of the rest of the functionality of the library is also available on the command line, as this document describes. @@ -51,14 +50,13 @@ Specifying Multiple Extracts **************************** You may create mutliple files specifying extracts. For instance, in addition to the -``ipums.yml`` described above, you might also have a file called ``ipums_with_race.yml`` +``ipums.yaml`` described above, you might also have a file called ``ipums_with_race.yaml`` which contains the following: .. code:: yaml description: Another extract collection: usa - api_version: 2 samples: - us2012b variables: @@ -70,17 +68,16 @@ Then the following command would submit and download this extract: .. code:: bash - ipums submit-and-download -k ipums_with_race.yml + ipums submit-and-download -k ipums_with_race.yaml Alternatively, the ``submit-and-download`` command also allows you to specify *multiple* -extracts simultaneously. To do so, specify the ``ipums_multiple.yml`` file as follows: +extracts simultaneously. To do so, specify the ``ipums_multiple.yaml`` file as follows: .. code:: yaml extracts: - description: Simple IPUMS extract collection: usa - api_version: 2 samples: - us2012b variables: @@ -88,7 +85,6 @@ extracts simultaneously. To do so, specify the ``ipums_multiple.yml`` file as fo - SEX - description: Another extract collection: usa - api_version: 2 samples: - us2012b variables: @@ -102,7 +98,7 @@ command: .. code:: bash - ipums submit-and-download -k ipums_multiple.yml + ipums submit-and-download -k ipums_multiple.yaml Step-by-Step ************ @@ -114,7 +110,7 @@ functionlaity is available via the ``submit``, ``check``, and ``download`` comma .. code:: bash - ipums submit -k ipums.yml + ipums submit -k ipums.yaml # Your extract for collection usa has been successfully submitted with number 10 ipums check -k 10 diff --git a/docs/source/extracts.rst b/docs/source/extracts.rst deleted file mode 100644 index a36264b..0000000 --- a/docs/source/extracts.rst +++ /dev/null @@ -1,474 +0,0 @@ -.. extracts - -.. currentmodule:: ipumspy - -IPUMS Extracts -============== - -IPUMS-py can be used to read extracts made via the IPUMS web interface into python. This page discusses how to request an IPUMS extract via API using IPUMS-py. - -Extract Definition ------------------- - -An extract is defined by: - -1. A data collection name -2. A list of IPUMS sample IDs from that collection -3. A list of IPUMS variable names from that collection - -IPUMS metadata is not currently accessible via API. Sample IDs and IPUMS variable names can be browsed via the data collection's website. See the table below for data collection abreviations and links to sample IDs and variable browsing. Note that not all IPUMS data collections are currently available via API. The table below will be -filled in as new IPUMS data collections become accessible via API. - -.. _collection availability table: - -.. list-table:: IPUMS data collections metadata resources - :widths: 25 25 25 25 - :header-rows: 1 - :align: center - - * - IPUMS data collection - - collection IDs - - sample IDs - - variable names - * - `IPUMS USA `__ - - usa - - `usa samples `__ - - `usa variables `__ - * - `IPUMS CPS `__ - - cps - - `cps samples `__ - - `cps variables `__ - * - `IPUMS International `__ - - ipumsi - - `ipumsi samples `__ - - `ipumsi variables `__ - * - `IPUMS ATUS `__ - - atus - - `atus samples `__ - - `atus variables `__ - * - `IPUMS AHTUS `__ - - ahtus - - `ahtus samples `__ - - `ahtus variables `__ - * - `IPUMS MTUS `__ - - mtus - - `mtus samples `__ - - `mtus variables `__ - * - `IPUMS NHIS `__ - - nhis - - `nhis samples `__ - - `nhis variables `__ - * - `IPUMS MEPS `__ - - meps - - `meps samples `__ - - `meps variables `__ - - -Extract Objects ---------------- - -All IPUMS data collection currently supported by `ipumspy` are microdata collections; extracts for these data collections can be constructed and submitted to the IPUMS API using the :class:`ipumspy.api.extract.MicrodataExtract` class. The minimum required arguments are 1) an IPUMS collection id, 2) a list of sample ids, and 3) a list of variable names. - -For example: - -.. code:: python - - extract = MicrodataExtract( - "usa", - ["us2012b"], - ["AGE", "SEX"], - ) - -instantiates a MicrodataExtract object for the IPUMS USA data collection that includes the us2012b (2012 PRCS) sample, and the variables AGE and SEX. - -IPUMS extracts can be requested as rectangular or hierarchical files. The ``data_structure`` argument defaults to ``{"rectangular": {"on": "P"}}`` to request a rectangular, person-level extract. The code snippet below requests a hierarchical USA extract. - -.. code:: python - - extract = MicrodataExtract( - "usa", - ["us2012b"], - ["AGE", "SEX"], - data_structure={"hierarchical": {}} - ) - - -Some IPUMS data collections offer rectangular files on non-person record types. For example, IPUMS ATUS offers rectangular files at the activity level and IPUMS MEPS offers rectangular files at the round level. Below is an example of an IPUMS MEPS extract object that is rectanularized on round. - -.. code:: python - - extract = MicrodataExtract( - "meps", - ["mp2016"], - ["AGE", "SEX", "PREGNTRD"], - data_structure={"rectangular": {"on": "R"}} - ) - -The table below shows the available data structures and the IPUMS data collections for which each is valid. - -.. _collection data structures table: - -.. list-table:: IPUMS data structures - :widths: 23 40 18 - :header-rows: 1 - :align: center - - * - data structure - - syntax - - collections - * - rectangular on Person (default) - - ``data_structure={"rectangular": {"on": "P"}}`` - - all IPUMS microdata collections - * - hierarchical - - ``data_structure={"hierarchical": {}}`` - - all IPUMS microdata collections - * - rectangular on Activity - - ``data_structure={"rectangular": {"on": "A"}}`` - - atus, ahtus, mtus - * - rectangular on Round - - ``data_structure={"rectangular": {"on": "R"}}`` - - meps - * - rectangular on Injury - - ``data_structure={"rectangular": {"on": "R"}}`` - - nhis - * - household only - - ``data_structure={"householdOnly": {}}`` - - usa - -Note that some types of records are only available as part of hierarchical extracts. This is true of IPUMS ATUS "Who" and "Eldercare" [*]_ records and of IPUMS MEPS "Event", "Condition", and "Prescription Medications" record types. - -Users also have the option to specify a data format and an extract description when creating an extract object. - -.. code:: python - - extract = MicrodataExtract( - "usa", - ["us2012b"], - ["AGE", "SEX"], - data_format="csv", - description="My first IPUMS USA extract!" - ) - - - -Once an extract object has been created, the extract must be submitted to the API. - -.. code:: python - - from ipumspy import IpumsApiClient, MicrodataExtract - - IPUMS_API_KEY = your_api_key - DOWNLOAD_DIR = Path(your_download_dir) - - ipums = IpumsApiClient(IPUMS_API_KEY) - - # define your extract - extract = MicrodataExtract( - "usa", - ["us2012b"], - ["AGE", "SEX"], - ) - - # submit your extract - ipums.submit_extract(extract) - -Once an extract has been submitted, an extract ID number will be assigned to it. - -.. code:: python - - extract.extract_id - -returns the extract id number assigned by the IPUMS extract system. In the case of your first extract, this code will return - -.. code:: python - - 1 - -You can use this extract ID number along with the data collection name to check on or download -your extract later if you lose track of the original extract object. - -Extract status --------------- - -After your extract has been submitted, you can check its status using - -.. code:: python - - ipums.extract_status(extract) - -returns: - -.. code:: python - - 'started' - -While IPUMS retains all of a user's extract definitions, after a certain period, the extract data and syntax files are purged from the IPUMS cache - these extracts are said to be "expired". Importantly, if an extract's data and syntax files have been removed, the extract is still considered to have been completed, and :meth:`.extract_status()` will return "completed." - -.. code:: python - - # extract number 1 has expired - ipums.extract_status(collection="usa", extract="1") - -returns: - -.. code:: python - - 'completed' - -If an extract has expired: - -.. code:: python - - ipums.extract_is_expired(collection="usa", extract="1") - - -returns: - -.. code:: python - - True - -For extracts that have expired, the data collection name and extract ID number can be used to re-create and re-submit the old extract. **Note that re-creating and re-submitting a expired extract results in a new extract with its own unique ID number!** - -.. code:: python - - # create a UsaExtract object from the expired extract definition - renewed_extract = ipums.get_extract_by_id(collection="usa", extract_id=1) - - # submit the renewed extract to re-generate the data and syntax files - resubmitted_extract = ipums.submit_extract(renewed_extract) - - resubmitted_extract.extract_id - -returns: - -.. code:: python - - 2 - -Extract Features ----------------- - -IPUMS Extract features can be added or updated before an extract request is submitted. This section demonstrates adding features to the following IPUMS CPS extract. - -.. code:: python - - extract = MicrodataExtract( - "cps", - ["cps2022_03s"], - ["AGE", "SEX", "RACE"], - ) - -Attach Characteristics -~~~~~~~~~~~~~~~~~~~~~~ - -IPUMS allows users to create variables that reflect the characteristics of other household members. The example below uses the :meth:`.attach_characteristics()` method to attach the spouse's AGE value, creating a new variable called SEX_SP in the extract that will contain the age of a person's spouse if they have one and be 0 otherwise. The :meth:`.attach_characteristics()` method takes the name of the variable to attach and the household member whose values the new variable will include. Valid household members include "spouse", "mother", "father", and "head". - -.. code:: python - - extract.attach_characteristics("SEX", ["spouse"]) - -The following would add variables for the RACE value of both parents: - -.. code:: python - - extract.attach_characteristics("RACE", ["mother", "father"]) - -Select Cases -~~~~~~~~~~~~ - -IPUMS allows users to limit their extract based on values of included variables. The code below uses the :meth:`.select_cases()` to select only the female records in the example IPUMS CPS extract. This method takes a variable name and a list of values for that variable for which to include records in the extract. Note that the variable must be included in the IPUMS extract object in order to use this feature; also note that this feature is only available for categorical varaibles. - -.. code:: python - - extract.select_cases("SEX", ["2"]) - -The :meth:`.select_cases()` method defaults to using "general" codes to select cases. Some variables also have detailed codes that can be used to select cases. Consider the following example extract of the 2021 ACS data from IPUMS USA: - -.. code:: python - - extract = MicrodataExtract( - "usa", - ["us2021a"], - ["AGE", "SEX", "RACE"] - ) - -In IPUMS USA, the `RACE `_ variable has both general and detailed codes. A user interested in respondents who identify themselves with two major race groups can use general codes: - -.. code:: python - - extract.select_cases("RACE", ["8"]) - -A user interested in respondents who identify as both White and Asian can use detailed case selection to only include those chose White and another available Asian cateogry. To do this, in addition to specifying the correct detailed codes, set the `general` flag to `False`: - -.. code:: python - - extract.select_cases("RACE", - ["810", "811", "812", "813", "814", "815", "816", "818"], - general=False) - -By default, case selection includes only individuals with the specified values for the specified variables. In the previous example, only persons who identified as both White and Asian are included in the extract. To make an extract that contains individuals in households that include an individual who identifies as both White and Asian, set the ``case_select_who`` flag to ``"households"`` when instantiating the extract object. The code snippet below creates such an extract. Note that whether to select individuals or households must be specified at the extract level, while what values to select on and whether these values are general or detailed codes is specified at the variable level. - -.. code:: python - - extract = MicrodataExtract( - "usa", - ["us2021a"], - ["AGE", "SEX", "RACE"], - case_select_who = "households" - ) - extract.select_cases("RACE", - ["810", "811", "812", "813", "814", "815", "816", "818"], - general=False) - - - -Add Data Quality Flags -~~~~~~~~~~~~~~~~~~~~~~ - -Data quality flags can be added to an extract on a per-variable basis or for the entire extract. The CPS extract example above could be re-defined as follows in order to add all available data quality flags: - -.. code:: python - - extract = MicrodataExtract( - "cps", - ["cps2022_03s"], - ["AGE", "SEX", "RACE"], - data_quality_flags=True - ) - -This extract specification will add data quality flags for all variables in the variable list to the extract for which data quality flags exist in the sample(s) in the samples list. - -Data quality flags can also be selected for specific variables using the :meth:`.add_data_quality_flags()` method. - -.. code:: python - - # add the data quality flag for AGE to the extract - extract.add_data_quality_flags("AGE") - - # note that this method will also accept a list! - extract.add_data_quality_flags(["AGE", "SEX"]) - -.. _Using Variable Objects to Include Extract Features: - -Using Variable Objects to Include Extract Features -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -It is also possible to define all variable-level extract features when the IPUMS extract object is first defined using :class:`ipumspy.api.extract.Variable` objects. The example below defines an IPUMS CPS extract that includes a variable for the age of the spouse (``attached_characteristics``), limits the sample to women (``case_selections``), and includes the data quality flag for RACE (``data_quality_flags``). - -.. code:: python - - fancy_extract = MicrodataExtract( - "cps", - ["cps2022_03s"], - [ - Variable(name="AGE", - attached_characteristics=["spouse"]), - Variable(name="SEX", - case_selections={"general": ["2"]}), - Variable(name="RACE", - data_quality_flags=True) - ] - ) - -Time Use Variables ------------------- -The IPUMS Time Use collections (ATUS, AHTUS, and MTUS) offer a special type of variable called time use variables. These variables correspond to the number of minutes a respondent spent during the 24-hour reference period on activities that match specific criteria. Within time use variables, there are two different variable types: "system" time use variables that IPUMS offers pre-made for users based on common definitions of activities and "user-defined" time use variables that users can construct based on their own criteria using the IPUMS web interface for these collections. Currently time use variable creation is not supported via the IPUMS API. However, users can request system time use variables as well as their own custom user-defined time use variables that they have previously created in the IPUMS web interface and saved to their user account via the IPUMS API. - -Because time use variables are a special type of variable, they need to be requested as time use variables specifically and cannot be added to the `varibles` argument list with non-time use variables. Below is an example of an IPUMS ATUS extract that contains variables AGE and SEX as well as the system time use variable BLS_PCARE. - -.. code:: python - - atus_extract = MicrodataExtract( - collection="atus", - samples=["at2016"], - variables=["AGE", "SEX"], - time_use_variables=["BLS_PCARE"] - ) - -Like other variables, time use variables can also be passed to :class:`ipumspy.api.extract.MicrodataExtract` as a list of :class:`ipumspy.api.extract.TimeUseVariable` objects. - -.. code:: python - - atus_extract = MicrodataExtract( - collection="atus", - samples=["at2016"], - variables=["AGE", "SEX"], - time_use_variables=[TimeUseVariable(name="BLS_PCARE")] - ) - -This approach may be simpler when including user-defined timeuse variables in your extract, as user-defined time use variables also have an owner attribute that must be specified. The owner field contains the email address associated with your IPUMS account. - -.. code:: python - - atus_extract = MicrodataExtract( - collection="atus", - samples=["at2016"], - variables=["AGE", "SEX"], - time_use_variables=[ - TimeUseVariable(name="BLS_PCARE"), - TimeUseVariable(name="MY_CUSTOM_TUV", owner="newipumsuser@gmail.com") - ] - ) - -IPUMS ATUS Sample Members -------------------------- - -Though time use information is only available for designated respondents in IPUMS ATUS, users may also wish to include household members of these respondents and/or non-respondents in their IPUMS ATUS extracts. These "sample members" can be included by using the ``sample_members`` key word argument. The example below includes both household members of ATUS respondents and non-respondents alongside ATUS respondents (included by default). - -.. code:: python - - atus_extract = MicrodataExtract( - collection="atus", - samples=["at2016"], - variables=["AGE", "SEX"], - time_use_variables=[ - TimeUseVariable(name="BLS_PCARE"), - TimeUseVariable(name="MY_CUSTOM_TUV", owner="newipumsuser@gmail.com") - ], - sample_members={ - "include_non_respondents": True, - "include_household_members": True - } - ) - -Unsupported Extract Features ----------------------------- - -Not all features available through the IPUMS extract web UI are currently supported for extracts made via API. -For a list of supported and unsupported features for each IPUMS data collection, see `the developer documentation `__. -This list will be updated as more features become available. - -Extract Histories ------------------ -``ipumspy`` offers several ways to peruse your extract history for a given IPUMS data collection. - -:meth:`.get_previous_extracts()` can be used to retrieve your 10 most recent extracts for a given collection. The limit can be set to a custom n of most recent previous extracts. - -.. code:: python - - from ipumspy import IpumsApiClient - - ipums = IpumsApiClient("YOUR_API_KEY") - - # get my 10 most-recent USA extracts - recent_extracts = ipums.get_previous_extracts("usa") - - # get my 20 most-recent CPS extracts - more_recent_extracts = ipums.get_previous_extracts("cps", limit=20) - -The :meth:`.get_extract_history()` generator makes it easy to filter your extract history to pull out extracts with certain variables, samples, features, file formats, etc. By default, this generator returns pages of extract definitions of the maximum possible size of 500 extract definitions per page. Page size can be set to a lower number using the ``page_size`` argument. - -.. code:: python - - # make a list of all of my extracts from IPUMS CPS that include the variable STATEFIP - extracts_with_state = [] - # get pages with 100 CPS extracts per page - for page in ipums.get_extract_history("cps", page_size=100): - for ext in page["data"]: - extract_obj = MicrodataExtract(**ext["extractDefinition"]) - if "STATEFIP" in [var.name for var in extract_obj.variables]: - extracts_with_state.append(extract_obj) - - -.. [*] Note that IPUMS ATUS Eldercare records will be included in a hierarchical extract automatically if Eldercare variables are selected. There is no API equivalent to the "include eldercare" checkbox in the Extract Data Structure menu in the IPUMS ATUS web interface. \ No newline at end of file diff --git a/docs/source/getting_started.rst b/docs/source/getting_started.rst index 7e3f903..b34793a 100644 --- a/docs/source/getting_started.rst +++ b/docs/source/getting_started.rst @@ -22,11 +22,15 @@ Install with ``conda``: conda install -c conda-forge ipumspy +.. _ipumspy readers: + Read an IPUMS extract --------------------- -The following code parses an IPUMS extract DDI xml codebook and data file and returns a pandas data frame. -Both fixed-width and csv files are supported. +For microdata collections, ipumspy provides methods to parse DDI xml codebooks and load data files into +pandas ``DataFrame`` objects. Both fixed-width and csv files are supported. + +For example: .. code:: python @@ -39,51 +43,79 @@ Both fixed-width and csv files are supported. IPUMS API Wrappers for Python ----------------------------- -``ipumspy`` provides an easy-to-use Python wrapper for IPUMS API endpoints. +ipumspy provides an easy-to-use Python wrapper for IPUMS API endpoints. -Quick Start -*********** +.. _get an api key: -Once you have created a user account for your data collection of interest generated an API key. -This quick start example uses `IPUMS USA `__. Note that not all IPUMS data collections are available via API. For an up-to-date list of available collections and links to sample and variable information, see the :ref:`collection availability table`. +Get an API Key +************** -.. code:: python +To interact with the IPUMS API, you'll need to register for access with the IPUMS project you'll be +using. If you have not yet registered, you can find the link to register for each +project at the top of its website, which can be accessed from the `IPUMS homepage `__. - from pathlib import Path +Once you're registered, you'll be able to create an `API key `__. - from ipumspy import IpumsApiClient, UsaExtract, readers, ddi +For security reasons, we recommend storing your key in an environment variable rather than including it in your code. +The Conda documentation provides +`instructions for saving environment variables `__ +in conda environments for different operating systems. The example code on this page assumes that the +API key is stored in an environment variable called ``IPUMS_API_KEY``. - IPUMS_API_KEY = your_api_key - DOWNLOAD_DIR = Path(your_download_dir) +A Simple Example +**************** - ipums = IpumsApiClient(IPUMS_API_KEY) +To request IPUMS data via API, initialize an API client using your API key: -Note that for security reasons it is recommended that you store your IPUMS API key in an environment variable rather than including it in your code. +.. code:: python -To define an IPUMS USA extract, you need to pass a list of sample IDs and a list of IPUMS USA variable names. + import os + from pathlib import Path + from ipumspy import IpumsApiClient, MicrodataExtract, readers, ddi -IPUMS USA sample IDs can be found on the `IPUMS USA website `__. + IPUMS_API_KEY = os.environ.get("IPUMS_API_KEY") + + ipums = IpumsApiClient(IPUMS_API_KEY) -IPUMS USA variables can be browsed via the `IPUMS USA extract web UI `__. +Next, create an extract definition that contains the specifications for the data you wish to request and download. +For instance, we can request 2012 Puerto Rico Community Survey data for age and sex from +`IPUMS USA `__ with the following: -Source variables can be requested using their short or long form variable names. Short form source variable names can be viewed by clicking `Display Options` on the `Select Data` page and selecting the `short` option under `Source variable names`. +.. code:: python + + # Create an extract definition + extract = MicrodataExtract( + collection="usa", + description="Sample USA extract", + samples=["us2012b"], + variables=["AGE", "SEX"], + ) + +.. seealso:: + The :doc:`IPUMS API client page` contains more detailed information on + supported data collections and available extract definition parameters. + + +Submit the extract to the IPUMS servers. After waiting for the extract to finish processing, you can download the data: .. code:: python - # Submit an API extract request - extract = UsaExtract( - ["us2012b"], - ["AGE", "SEX"], - ) + # Submit the extract request ipums.submit_extract(extract) print(f"Extract submitted with id {extract.extract_id}") + #> Extract submitted with id 1 - # wait for the extract to finish + # Wait for the extract to finish ipums.wait_for_extract(extract) # Download the extract + DOWNLOAD_DIR = Path() ipums.download_extract(extract, download_dir=DOWNLOAD_DIR) +For microdata collections, you can load your data using ipumspy readers described :ref:`above`: + +.. code:: python + # Get the DDI ddi_file = list(DOWNLOAD_DIR.glob("*.xml"))[0] ddi = readers.read_ipums_ddi(ddi_file) @@ -91,50 +123,8 @@ Source variables can be requested using their short or long form variable names. # Get the data ipums_df = readers.read_microdata(ddi, DOWNLOAD_DIR / ddi.file_description.filename) -If you lose track of the ``extract`` object for any reason, you may check the status -and download the extract using only the name of the ``collection`` and the ``extract_id``. - -.. code:: python - - # check the extract status - extract_status = ipums.extract_status(extract=[extract_id], collection=[collection_name]) - print(f"extract {extract_id} is {extract_status}") - - # when the extract status is "completed", then download - ipums.download_extract(extract=[extract_id], collection=[collection_name]) - -Specifying an Extract as a File -******************************* - -A goal of IPUMS-py is to make it easier to share IPUMS extracts with other researchers. -For instance, we envision being able to include an ``ipums.yml`` file to your analysis -code which would allow other researchers to download *exactly* the extract that you -utilize in your own analysis. - -To pull the extract we specified made above, create a file called ``ipums.yml`` that -contains the following: - -.. code:: yaml - - description: Simple IPUMS extract - collection: usa - api_version: beta - samples: - - us2012b - variables: - - AGE - - SEX - -Then you can run the following code: - -.. code:: python - - import yaml - from ipumspy import extract_from_dict - - with open("ipums.yml") as infile: - extract = extract_from_dict(yaml.safe_load(infile)) - -Alternatively, you can utilize the :doc:`CLI `. +Aggregate data collection data can be loaded with other python libraries. See :ref:`reading-aggregate-data` for +examples. -For more information on the IPUMS API, visit the `IPUMS developer portal `__. \ No newline at end of file +For additional information about the IPUMS API as well as technical documentation, visit the +`IPUMS developer portal `__. \ No newline at end of file diff --git a/docs/source/index.rst b/docs/source/index.rst index bbf5ae3..6b2e156 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -3,20 +3,25 @@ What is IPUMS? -------------- -IPUMS provides census and survey data from around the world integrated across time and space. IPUMS integration and documentation makes it easy to study change, conduct comparative research, merge information across data types, and analyze individuals within family and community contexts. Data and services available free of charge. More information on IPUMS data collections can be found at `ipums.org `_. +IPUMS provides census and survey data from around the world integrated across time and space. IPUMS integration and documentation makes +it easy to study change, conduct comparative research, merge information across data types, and analyze individuals within family +and community contexts. Data and services available free of charge. More information on IPUMS data collections can be +found at `ipums.org `_. What is ipumspy? ---------------- -``ipumspy`` is a collection of python tools for working with data from `IPUMS `_ and for accessing that data via API. +ipumspy is a collection of python tools for working with data downloaded from `IPUMS `_ and for accessing that +data via the `IPUMS API `_. -Currently only IPUMS microdata collections are supported; we hope to add support for working with spatial data in the future. +ipumspy can only be used to request data for IPUMS data collections supported by the IPUMS API. See the :ref:`collection support table` table +for a list of currently supported collections. Support for other IPUMS data collections will be added as they become available via API. -``ipumspy`` can only be used to make extract requests for IPUMS data collections that are available via API. These collections are listed in the :ref:`collection availability table` table. Support for other IPUMS data collections will be added as they become available via API. -For more information about the IPUMS API program, IPUMS account registration, and API keys, see the `IPUMS developer portal `_. - -``ipumspy`` can also be used to read and analyze microdata extracts made through the IPUMS website for collections unavailable via API. +For more information about the IPUMS API program, IPUMS account registration, and API keys, see +the `IPUMS developer portal `_. +Even for collections not yet supported by the API, ipumspy can be used to read and analyze +data downloaded from the IPUMS website. Releases -------- @@ -26,7 +31,7 @@ This library's :doc:`change-log` details changes and fixes made with each releas How to Cite ----------- -If you use ``ipumspy`` in the context of academic or industry research, please +If you use ipumspy in the context of academic or industry research, please cite IPUMS. For any given extract, the appropriate citation may be found in the accompanying DDI at: @@ -37,7 +42,7 @@ accompanying DDI at: License and Credits ------------------- -``ipumspy`` is licensed under the `Mozilla Public License Version 2.0 `_. +ipumspy is licensed under the `Mozilla Public License Version 2.0 `_. Indices and tables @@ -51,8 +56,7 @@ Indices and tables :hidden: getting_started - extracts - variables + ipums_api/index reading_data cli diff --git a/docs/source/ipums_api/index.rst b/docs/source/ipums_api/index.rst new file mode 100644 index 0000000..b4070b2 --- /dev/null +++ b/docs/source/ipums_api/index.rst @@ -0,0 +1,396 @@ +.. api_client + +.. currentmodule:: ipumspy + +IPUMS API +========= + +ipumspy provides a framework for users to submit extract requests and download IPUMS data via the IPUMS API. + +API Assets +---------- + +The IPUMS API provides two asset types: + +- **IPUMS extract** endpoints can be used to submit extract requests for processing and download completed extract files. +- **IPUMS metadata** endpoints can be used to discover and explore available IPUMS data as well as retrieve codes, names, + and other extract parameters necessary to form extract requests. + +.. _supported collections: + +Supported IPUMS Collections +--------------------------- + +IPUMS consists of multiple collections that provide different data products. +These collections fall into one of two categories: + +- :doc:`Microdata` collections distribute data for individual survey units, like people or households. +- :doc:`Aggregate data` collections distribute summary tables of aggregate statistics for particular + geographic units, and may also provide corresponding GIS mapping files. + +Not all IPUMS collections are currently supported by the IPUMS API. The table below summarizes the +available features for all collections currently supported by the API: + +.. _collection support table: + +.. list-table:: Supported data collections + :widths: 28 20 20 16 16 + :header-rows: 1 + :align: center + + * - IPUMS data collection + - Data type + - Collection ID + - Request & download data + - Browse metadata + * - `IPUMS USA `__ + - Microdata + - ``usa`` + - **X** + - + * - `IPUMS CPS `__ + - Microdata + - ``cps`` + - **X** + - + * - `IPUMS International `__ + - Microdata + - ``ipumsi`` + - **X** + - + * - `IPUMS ATUS `__ + - Microdata + - ``atus`` + - **X** + - + * - `IPUMS AHTUS `__ + - Microdata + - ``ahtus`` + - **X** + - + * - `IPUMS MTUS `__ + - Microdata + - ``mtus`` + - **X** + - + * - `IPUMS NHIS `__ + - Microdata + - ``nhis`` + - **X** + - + * - `IPUMS MEPS `__ + - Microdata + - ``meps`` + - **X** + - + * - `IPUMS NHGIS `__ + - Aggregate data + - ``nhgis`` + - **X** + - **X** + +.. tip:: + While the IPUMS API does not yet provide comprehensive metadata for microdata collections, you can use + :py:meth:`.get_all_sample_info` to retrieve a dictionary of available sample IDs. + +Note that ipumspy may not necessarily support all the functionality currently supported by +the IPUMS API. See the `API documentation `__ for more information +about its latest features. + +Get an API Key +-------------- + +Before you can interact with the IPUMS API, you'll need to make sure you've +:ref:`obtained and set up ` your API key. + +You can then initialize an API client using your key. (The following assumes your +key is stored in the ``IPUMS_API_KEY`` environment variable as described in the link above.) + +.. code:: python + + import os + from pathlib import Path + from ipumspy import IpumsApiClient, MicrodataExtract, save_extract_as_json + + IPUMS_API_KEY = os.environ.get("IPUMS_API_KEY") + + ipums = IpumsApiClient(IPUMS_API_KEY) + +Extract Objects +--------------- + +To request IPUMS data via the IPUMS API, you need to first create an extract request object, +which contains the parameters that define the content, format, and layout for the data you'd like +to download. + +IPUMS extract requests can be constructed and submitted to the IPUMS API using either + +- The :class:`MicrodataExtract` class (for microdata collections) +- The :class:`AggregateDataExtract` class (for aggregate data collections) + +For instance, the following defines a simple IPUMS USA extract request for the +AGE, SEX, RACE, STATEFIP, and MARST variables from the 2018 and 2019 American Community Survey (ACS): + +.. code:: python + + extract = MicrodataExtract( + collection="usa", + description="Sample USA extract", + samples=["us2018a", "us2019a"], + variables=["AGE", "SEX", "RACE", "STATEFIP", "MARST"], + ) + +.. seealso:: + The available extract definition options vary across collections. See the collection-specific + documentation for details about specifying more complex extracts for each type: + + :doc:`Microdata extracts` - Information on microdata extract parameters + + :doc:`Aggregate data extracts` - Information on aggregate data extract parameters + +.. _submit-extract: + +Submit an Extract Request +------------------------- + +Once you've created an extract object, you can submit it to the IPUMS servers for processing: + +.. code:: python + + ipums.submit_extract(extract) + +If the extract is succesfully submitted, it will receive an ID number: + +.. code:: python + + print(extract.extract_id) + #> 1 + +You can use this extract ID number along with the data collection name to check on or +download your extract later if you lose track of the original extract object. + +Download an Extract +------------------- + +It may take some time for the IPUMS servers to process your extract request. You can check the +current status of a request: + +.. code:: python + + print(ipums.extract_status(extract)) + #> started + +Instead of repeatedly checking the status, you can explicitly wait for the extract +to complete before attempting to download it: + +.. code:: python + + ipums.wait_for_extract(extract) + +At this point, you can safely download the extract: + +.. code:: python + + DOWNLOAD_DIR = Path("") + ipums.download_extract(extract, path=DOWNLOAD_DIR) + +Extract Status +-------------- + +If you lose track of the ``extract`` object for any reason, you may check the status +and download the extract using only the name of the ``collection`` and the ``extract_id``. + +.. code:: python + + # Check the extract status + extract_status = ipums.extract_status(extract=1, collection="usa") + print(f"extract is {extract_status}") + #> extract is started + +You can also wait for and download an extract using this unique identifier: + +.. code:: python + + ipums.wait_for_extract(extract=1, collection="usa") + ipums.download_extract(extract=1, collection="usa") + +Expired Extracts +**************** + +While IPUMS retains all of a user's extract definitions, after a certain period, the extract data and syntax +files are purged from the IPUMS cache—these extracts are said to be "expired". Importantly, if an extract's data and +syntax files have been removed, the extract is still considered to have been completed, and +:meth:`.extract_status()` will return "completed." + +.. code:: python + + # Extract number 1 has expired, but status listed as completed + extract_status = ipums.extract_status(extract=1, collection="usa") + + print(extract_status) + #> completed + +You can confirm whether an extract has expired with the following: + +.. code:: python + + is_expired = ipums.extract_is_expired(extract=1, collection="usa") + + print(is_expired) + #> True + +For extracts that have expired, the data collection name and extract ID number can be used to +re-create and re-submit the old extract. + +.. attention:: + Note that re-creating and "re-submitting" an expired extract results in a **new** extract with its own unique ID number! + +.. code:: python + + # Create a MicrodataExtract object from the expired extract definition + renewed_extract = ipums.get_extract_by_id(collection="usa", extract_id=1) + + # Submit the renewed extract to re-generate the data and syntax files + resubmitted_extract = ipums.submit_extract(renewed_extract) + + print(resubmitted_extract.extract_id) + #> 2 + +Sharing Extract Definitions +--------------------------- + +ipumspy also makes it easier to share IPUMS extracts with collaborators. +By saving your extract as a standalone file, you can send it to other researchers +or reviewers, allowing them to generate an identical extract and download +the same data used in your analysis. + +Collaborators submitting your extract definition will need to be registered with the +data collection represented in the extract and have their own API key to succesfully +submit the request. The request will be processed under their account, but the data in the resulting +extract will be identical. + +.. note:: + Sharing IPUMS extract definitions using files will ensure that your collaborators are able to submit the + same extract definition to the IPUMS extract system. However, IPUMS data are updated to fix errors as we + become aware of them and preserving extract definitions in a file do not insulate against these types of + changes to data. + + In other words, if the IPUMS data included in the extract definition change between + submission of extracts based on this definition, the resulting downloaded files will not be identical. + IPUMS collections keep a log of errata and other changes on their websites. + + +Using JSON +********** + +Use the following to save your extract object in JSON format: + +.. code:: python + + save_extract_as_json(extract, filename="my_extract.json") + +If you send this file to a collaborator, they can load it into ipumspy and submit it +themselves: + +.. code:: python + + import os + from ipumspy import IpumsApiClient, define_extract_from_json + + IPUMS_API_KEY = os.environ.get("IPUMS_API_KEY") + ipums = IpumsApiClient(IPUMS_API_KEY) + + extract = define_extract_from_json("my_extract.json") + ipums.submit_extract(extract) + +.. _using-yaml: + +Using YAML +********** + +You can also store your extract in YAML format. For instance, to re-create +the extract we made above, we could save a file called ``ipums.yaml`` +(for instance) with the following contents: + +.. code:: yaml + + description: Sample USA extract + collection: usa + samples: + - us2018a + - us2019a + variables: + - AGE + - SEX + - RACE + - STATEFIP + - MARST + +We can load the file into ipumspy by parsing the file into a dictionary +and then converting the dictionary to a +:class:`MicrodataExtract` object. + +.. code:: python + + import yaml + from ipumspy import extract_from_dict + + with open("ipums.yaml") as infile: + extract = extract_from_dict(yaml.safe_load(infile)) + +.. tip:: + You can also use the ipumspy :doc:`CLI<../cli>` to easily submit extract requests for + definitions saved in YAML format. + +.. _extract-histories: + +Extract Histories +----------------- + +ipumspy offers two ways to peruse your extract history for a given IPUMS data collection. + +:meth:`.get_previous_extracts()` can be used to retrieve your most recent extracts for a +given collection. By default, it retrieves your previous 10 extracts, but you can adjust +the ``limit`` argument to retrieve more or fewer records: + +.. code:: python + + from ipumspy import IpumsApiClient + + ipums = IpumsApiClient("YOUR_API_KEY") + + # get my 10 most-recent USA extracts + recent_extracts = ipums.get_previous_extracts("usa") + + # get my 20 most-recent CPS extracts + more_recent_extracts = ipums.get_previous_extracts("cps", limit=20) + +Alternatively, the :meth:`.get_extract_history()` generator makes it easy to filter your extract history to +pull out extracts with certain features (e.g., variables, file formats, etc.). By default, this +generator returns pages of extract definitions of the maximum possible size of 500 extract definitions +per page. Page size can be set to a lower number using the ``page_size`` argument. + +Here, we filter our history to identify all our CPS extracts containing the ``STATEFIP`` variable: + +.. code:: python + + extracts_with_state = [] + + # Get pages with 100 CPS extracts per page + for page in ipums.get_extract_history("cps", page_size=100): + for ext in page["data"]: + extract_obj = extract_from_dict(ext["extractDefinition"]) + if "STATEFIP" in [var.name for var in extract_obj.variables]: + extracts_with_state.append(extract_obj) + +Browsing your extract history is a good way to identify previous extracts and re-submit them. + +.. tip:: + Specifying a memorable extract ``description`` when defining an extract object + can make it easier to identify the extract in your history in the future. + +.. toctree:: + :hidden: + + ipums_api_micro/index + ipums_api_aggregate/index diff --git a/docs/source/ipums_api/ipums_api_aggregate/index.rst b/docs/source/ipums_api/ipums_api_aggregate/index.rst new file mode 100644 index 0000000..85a4ec4 --- /dev/null +++ b/docs/source/ipums_api/ipums_api_aggregate/index.rst @@ -0,0 +1,274 @@ +.. ipumspy documentation for aggregate data + +.. currentmodule:: ipumspy + +Aggregate Data Extracts +======================= + +IPUMS aggregate data collections distribute aggregated statistics for a set of geographic units. + +Currently, `IPUMS NHGIS `__ is the only aggregate data collection +supported by the IPUMS API. + +Extract Objects +--------------- + +Construct an extract for an IPUMS aggregate data collection using the +:class:`AggregateDataExtract` class. +An ``AggregateDataExtract`` must contain an IPUMS collection ID +and at least one data source. IPUMS NHGIS provides 3 different types of data sources: + +- Datasets/data tables +- Time series tables +- Shapefiles + +We also recommend providing an extract description to make it easier to identify and +retrieve your extract in the future. + +For example: + +.. code:: python + + from ipumspy import AggregateDataExtract, Dataset + + extract = AggregateDataExtract( + collection="nhgis", + description="An NHGIS extract example", + datasets=[ + Dataset(name="1990_STF1", data_tables=["NP1", "NP2"], geog_levels=["county"]) + ] + ) + +This instantiates an ``AggregateDataExtract`` object for the IPUMS NHGIS data collection +that includes a request for county-level data from tables NP1 (total population) and NP2 (total families) of +the 1990 STF 1 decennial census file. + +After instantiation, an ``AggregateDataExtract`` object can be +:ref:`submitted to the API ` for processing. + +.. note:: + The IPUMS API provides a set of metadata endpoints for aggregate data collections that allow you + to browse available data sources and identify their associated API codes. + + You can also browse metadata interactively through the `NHGIS Data Finder `_. + + *TODO: cross-link to metadata docs when available* + +Datasets + Data Tables +---------------------- + +An IPUMS **dataset** contains a collection of **data tables** that each correspond to a particular tabulated summary statistic. +A dataset is distinguished by the years, geographic levels, and topics that it covers. For instance, 2021 1-year data from the +American Community Survey (ACS) is encapsulated in a single dataset. In other cases, a single census product will be split into +multiple datasets, typically based on the lowest-level geography for which a set of tables is available. See the +`NHGIS documentation `_ for more details. + +To request data contained in an IPUMS dataset, you need to specify the name of the dataset, name of the data table(s) to request +from that dataset, and the geographic level at which those tables should be aggregated. + +Use the :class:`Dataset ` class to specify these parameters. + +.. code:: python + + extract = AggregateDataExtract( + collection="nhgis", + description="An NHGIS example extract", + datasets=[ + Dataset(name="2000_SF1a", data_tables=["NP001A", "NP031A"], geog_levels=["state"]) + ], + ) + +Some datasets span multiple years and require a selection of ``years``: + +.. code:: python + + extract = AggregateDataExtract( + collection="nhgis", + description="An NHGIS example extract", + datasets=[ + Dataset( + name="1988_1997_CBPa", + data_tables=["NT004"], + geog_levels=["county"], + years=[1988, 1989, 1990], + ) + ], + ) + +.. tip:: + To select all years in a dataset, use ``years=["*"]``. + +You can also optionally request specific `breakdown values `__ +for a dataset with the ``breakdown_values`` keyword argument: + +.. code:: python + + extract = AggregateDataExtract( + collection="nhgis", + description="An NHGIS example extract", + datasets=[ + Dataset( + name="2000_SF1a", + data_tables=["NP001A", "NP031A"], + geog_levels=["state"], + breakdown_values=["bs21.ge01", "bs21.ge43"], # Urban + Rural breakdowns + ) + ], + ) + +By default, the first available breakdown (typically, the total count) will be selected. When retrieving a +previously submitted extract from the IPUMS API, you may notice a breakdown value code present in the extract +definition despite not explicitly requesting one when submitting the extract. + +For datasets with multiple breakdowns or data types (e.g., the American Community Survey contains both estimates +and margins of error), you can request that the data for each be provided in separate files or together in a +single file using the ``breakdown_and_data_type_layout`` argument. + +Geographic Extent Selection +*************************** + +When working with small geographies it can be computationally intensive to work with +nationwide data. To avoid this problem, you can request data from a specific geographic area +using the ``geographic_extents`` argument. + +The following extract requests ACS 5-year sex-by-age counts at the census block group level, but +only includes block groups that fall within Alabama and Arkansas (identified by their FIPS codes with +a trailing 0): + +.. code:: python + + extract = AggregateDataExtract( + collection="nhgis", + description="Extent selection example", + datasets=[ + Dataset(name="2018_2022_ACS5a", data_tables=["B01001"], geog_levels=["blck_grp"]), + Dataset(name="2017_2021_ACS5a", data_tables=["B01001"], geog_levels=["blck_grp"]) + ], + geographic_extents=["010", "050"] + ) + +Note that extent selection is *not* a dataset-specific parameter. That is, the selected extents +are applied to all datasets in the extract. It is not possible to request different extents for different +datasets in a single extract. + +.. note:: + Currently, NHGIS only supports state-level extent selection for census blocks and block groups. + +Time Series Tables +------------------ + +IPUMS NHGIS also provides `time series tables `_—longitudinal data sources that link comparable statistics from multiple +U.S. censuses in a single package. A table is comprised of one or more related time series, each +of which describes a single summary statistic measured at multiple times for a given geographic level. + +Use the :class:`TimeSeriesTable` class to add time series tables +to your extract request. + +Time series tables are already associated with a specific summary statistic, so they don't require an additional +selection of data tables as is required for NHGIS datasets. However, you will need to specify the geographic +level for the data: + +.. code:: python + + from ipumspy import AggregateDataExtract, TimeSeriesTable + + extract = AggregateDataExtract( + collection="nhgis", + description="An NHGIS example extract: time series tables", + time_series_tables=[TimeSeriesTable("CW3", geog_levels=["county", "state"])], + ) + +By default, a time series table request will provide data for all years available for that time series table. +You can select a subset of available years with the ``years`` argument: + +.. code:: python + + extract = AggregateDataExtract( + collection="nhgis", + description="An NHGIS example extract: time series tables", + time_series_tables=[ + TimeSeriesTable("CW3", geog_levels=["county", "state"], years=[1990, 2000]) + ], + ) + +For extract requests that contain time series tables, you can indicate the desired layout of the time +series data with the ``tst_layout`` argument. Timepoints can either be arranged in columns, rows, or split +into separate files (by default, time is arranged across columns). + +.. code:: python + + extract = AggregateDataExtract( + collection="nhgis", + description="An NHGIS example extract: time series tables", + time_series_tables=[ + TimeSeriesTable("CW3", geog_levels=["county", "state"], years=[1990, 2000]) + ], + tst_layout="time_by_row_layout", + ) + +Shapefiles +---------- + +IPUMS **shapefiles** contain geographic data for a given geographic level and year. +Typically, these files are composed of polygon geometries containing the boundaries of census reporting areas. + +Because there are no additional selection parameters for shapefiles, you can include them in your request +simply by specifying their names: + +.. code:: python + + AggregateDataExtract( + collection="nhgis", + shapefiles=["us_county_2021_tl2021", "us_county_2020_tl2020"] + ) + +Multiple Data Sources +--------------------- + +You can request any combination of datasets, time series tables, and shapefiles in a single extract. +For instance, to request spatial boundary data to go along with the tabular data requested in a set of +datasets: + +.. code:: python + + # Total state-level population from 2000 and 2010 decennial census + extract = AggregateDataExtract( + collection="nhgis", + description="An NHGIS example extract", + datasets=[ + Dataset(name="2000_SF1a", data_tables=["NP001A"], geog_levels=["state"]), + Dataset(name="2010_SF1a", data_tables=["P1"], geog_levels=["state"]) + ], + shapefiles=["us_state_2000_tl2010", "us_state_2010_tl2010"] + ) + +In some cases, data table codes are consistent across datasets. This is often the case for the American Community Survey +(ACS) datasets. This makes it easy to build an extract request for a specific data table for +several ACS years at once using list comprehensions. For instance: + +.. code:: python + + acs1_names = ["2017_ACS1", "2018_ACS1", "2019_ACS1"] + acs1_specs = [ + Dataset(name, data_tables=["B01001"], geog_levels=["state"]) for name in acs1_names + ] + + # Total state-level population from 2017-2019 ACS 1-year estimates + extract = AggregateDataExtract( + collection="nhgis", + description="An NHGIS example extract", + datasets=acs1_specs, + ) + +Data Format +----------- + +By default, NHGIS extracts are provided in CSV format with only a single header row. +If you like, you can request that your CSV data include a second header row containing +a description of each column's contents by setting ``data_format="csv_header"``. + +You can also request your data in +fixed-width format if so desired. Note that unlike for microdata projects, NHGIS does +not provide DDI codebook files (in XML format), which allow ipumspy to parse +microdata fixed-width files. Thus, loading an NHGIS fixed width file will require +manual work to parse the file correctly. diff --git a/docs/source/ipums_api/ipums_api_micro/index.rst b/docs/source/ipums_api/ipums_api_micro/index.rst new file mode 100644 index 0000000..d431d11 --- /dev/null +++ b/docs/source/ipums_api/ipums_api_micro/index.rst @@ -0,0 +1,402 @@ +.. ipumspy documentation for microdata extract objects + +.. currentmodule:: ipumspy + +Microdata Extracts +================== + +Extract Objects +--------------- + +Construct an extract for an IPUMS microdata collection using the +:class:`MicrodataExtract` class. +At a minimum, any ``MicrodataExtract`` must contain: + +1. An IPUMS collection ID +2. A list of sample IDs +3. A list of variable names. + +We also recommend providing an extract description to make it easier to +:ref:`identify and retrieve ` your extract in the future. + +For example: + +.. code:: python + + extract = MicrodataExtract( + collection="usa", + samples=["us2012b"], + variables=["AGE", "SEX"], + description="An IPUMS extract example" + ) + +This instantiates a ``MicrodataExtract`` object for the `IPUMS USA `__ +data collection that includes the us2012b (2012 PRCS) sample, and the variables AGE and SEX. + +After instantiation, a ``MicrodataExtract`` object can be +:ref:`submitted to the API ` for processing. + +Data Structures +--------------- + +IPUMS microdata extracts can be requested in rectangular, hierarhical, or household-only structures. + +- **Rectangular** data combine data for different record types into single records in the output. + For instance, rectangular-on-person data provide person-level records with all requested household + information attached to the persons in that household. +- **Hierarchical** data contain separate records for different record types. +- **Household-only** data contain household records without any person records. + +The ``data_structure`` argument defaults to ``{"rectangular": {"on": "P"}}``, which requests a +rectangular, person-level extract. The code snippet below requests a hierarchical USA extract +instead: + +.. code:: python + + extract = MicrodataExtract( + collection="usa", + samples=["us2012b"], + variables=["AGE", "SEX"], + description="An IPUMS extract example", + data_structure={"hierarchical": {}}, + ) + +Some IPUMS data collections offer rectangular files on non-person record types. For example, IPUMS +ATUS offers rectangular files at the activity level and IPUMS MEPS offers rectangular files at the +round level. + +To rectangularize on a different record type, adjust the ``"on"`` key. For instance, to rectangularize +an IPUMS MEPS extract on round records: + +.. code:: python + + extract = MicrodataExtract( + collection="meps", + samples=["mp2016"], + variables=["AGE", "SEX", "PREGNTRD"], + description="An IPUMS extract example", + data_structure={"rectangular": {"on": "R"}}, + ) + +The table below shows the available data structures and the IPUMS data collections for which each is valid. + +.. _collection data structures table: + +.. list-table:: IPUMS data structures + :widths: 23 40 18 + :header-rows: 1 + :align: center + + * - Data structure + - Syntax + - Collections + * - rectangular on Person (default) + - ``data_structure={"rectangular": {"on": "P"}}`` + - all IPUMS microdata collections + * - hierarchical + - ``data_structure={"hierarchical": {}}`` + - all IPUMS microdata collections + * - rectangular on Activity + - ``data_structure={"rectangular": {"on": "A"}}`` + - ``atus``, ``ahtus``, ``mtus`` + * - rectangular on Round + - ``data_structure={"rectangular": {"on": "R"}}`` + - ``meps`` + * - rectangular on Injury + - ``data_structure={"rectangular": {"on": "I"}}`` + - ``nhis`` + * - household only + - ``data_structure={"householdOnly": {}}`` + - ``usa`` + +Note that some types of records are only available as part of hierarchical extracts. This is true of +IPUMS ATUS "Who" and "Eldercare"[*]_ records and of IPUMS MEPS "Event", "Condition", and "Prescription Medications" +record types. + +Users also have the option to specify a desired file format when creating an extract object +by using the ``data_format`` argument: + +.. code:: python + + extract = MicrodataExtract( + collection="usa", + samples=["us2012b"], + variables=["AGE", "SEX"], + description="An IPUMS extract example", + data_format="csv", + ) + +.. _extract-features: + +Extract Features +---------------- + +Certain features of a :class:`MicrodataExtract` can be +added or updated before an extract request is submitted. This section +demonstrates adding features to the following IPUMS CPS extract. + +.. code:: python + + extract = MicrodataExtract( + collection="cps", + samples=["cps2022_03s"], + variables=["AGE", "SEX", "RACE"], + description="A CPS extract example" + ) + +Attach Characteristics +~~~~~~~~~~~~~~~~~~~~~~ + +IPUMS allows users to create variables that reflect the characteristics of other household +members. The example below uses the :meth:`.attach_characteristics()` method to attach the +spouse's AGE value, creating a new variable called SEX_SP in the extract that will contain the +age of a person's spouse if they have one and be 0 otherwise. + +The :meth:`.attach_characteristics()` method takes the name of the variable to attach and the +household member whose values the new variable will include. Valid household members +include "spouse", "mother", "father", and "head". + +.. code:: python + + extract.attach_characteristics("SEX", ["spouse"]) + +The following would add variables for the RACE value of both parents: + +.. code:: python + + extract.attach_characteristics("RACE", ["mother", "father"]) + +Select Cases +~~~~~~~~~~~~ + +IPUMS allows users to restrict the records included in their extract based on values of included +variables. In ipumspy, use :meth:`.select_cases` to do so. This method takes a variable name and +a list of values for that variable. The resulting extract will only include the records whose data +for that variable match the indicated values. + +For instance, the code below selects only the female records (code ``"2"``) in our example +IPUMS CPS extract. + +.. code:: python + + extract.select_cases("SEX", ["2"]) + +.. note:: + :meth:`.select_cases` can only be used with categorical variables, and the indictated + variables must already be present in the IPUMS extract object. + +Detailed Codes +************** + +The :meth:`.select_cases()` method defaults to using *general* codes to select cases. Some +variables also have *detailed* codes that can be used to select cases. Consider the following +example extract of the 2021 ACS data from IPUMS USA: + +.. code:: python + + extract = MicrodataExtract( + collection="usa", + samples=["us2021a"], + variables=["AGE", "SEX", "RACE"], + description="Case selection example" + ) + +In IPUMS USA, the `RACE `_ variable has +both general and detailed codes. A user interested in respondents who belong to +a general category, like "Two major races", can use general codes: + +.. code:: python + + # Select persons identifying as "Two major races" + extract.select_cases("RACE", ["8"]) + +However, to identify respondents belonging to more specific categories, you would need +to use detailed codes instead. For instance, to identify respondents who identify as both +White and Asian, you can use the detailed codes representing the intersection of White +and each of the other Asian response options (e.g., Chinese, Japanese, etc.). + +To do this, in addition to specifying the correct detailed codes, set the ``general`` +flag to ``False``: + +.. code:: python + + extract.select_cases("RACE", + ["810", "811", "812", "813", "814", "815", "816", "818"], + general=False) + +By default, case selection restricts the data to those *individuals* who +match the provided values for the indicated variables. Alternatively, you can create +an extract that includes *all* the individuals in households that contain at least one individual +who matches the case selection criteria. To do so, set the ``case_select_who`` flag to +``"households"`` when instantiating the extract object. + +.. code:: python + + extract = MicrodataExtract( + collection="usa", + samples=["us2021a"], + variables=["AGE", "SEX", "RACE"], + description="Case selection example", + case_select_who = "households" + ) + + extract.select_cases("RACE", + ["810", "811", "812", "813", "814", "815", "816", "818"], + general=False) + +Add Data Quality Flags +~~~~~~~~~~~~~~~~~~~~~~ + +Some IPUMS variables have been edited for missing, illegible, and inconsistent values. +Data quality flags indicate which values are edited or allocated. + +Data quality flags can be added to an extract for individual variables or for the entire extract. +The CPS extract example above could be re-defined as follows in order to add all available +data quality flags: + +.. code:: python + + extract = MicrodataExtract( + collection="cps", + samples=["cps2022_03s"], + variables=["AGE", "SEX", "RACE"], + data_quality_flags=True + ) + +This extract specification will add data quality flags for all of the extract's +variables, provided that the data quality flags exist in the extract's sample(s). + +Data quality flags can also be selected for specific variables using the :meth:`.add_data_quality_flags()` method. + +.. code:: python + + # add the data quality flag for AGE to the extract + extract.add_data_quality_flags("AGE") + + # note that this method will also accept a list! + extract.add_data_quality_flags(["AGE", "SEX"]) + +.. _Using Variable Objects to Include Extract Features: + +Variable Objects +~~~~~~~~~~~~~~~~ + +It is also possible to specify variable-level extract features when +defining a :class:`MicrodataExtract` using +:class:`Variable` objects. + +The example below defines an IPUMS CPS extract that includes a variable for the age of +the spouse (``attached_characteristics``), limits the sample to women (``case_selections``), and includes the +data quality flag for RACE (``data_quality_flags``). + +.. code:: python + + fancy_extract = MicrodataExtract( + collection="cps", + samples=["cps2022_03s"], + variables=[ + Variable(name="AGE", + attached_characteristics=["spouse"]), + Variable(name="SEX", + case_selections={"general": ["2"]}), + Variable(name="RACE", + data_quality_flags=True) + ], + description="A fancy CPS extract", + ) + +Time Use Variables +------------------ + +The IPUMS Time Use collections (ATUS, AHTUS, and MTUS) offer a special type of variable called +time use variables. These variables record to the number of minutes a respondent spent during +the 24-hour reference period on activities that match specific criteria. Time use variables +come in two different types: + +- **System** time use variables have been pre-made by IPUMS based on common definitions of activities. +- **User-defined** time use variables can be constructed by users based on their own criteria using + the IPUMS web interface. + +Currently time use variable *creation* is not supported via the IPUMS API. +However, users can use the IPUMS API to *request* system time use variables as well as their +own custom user-defined time use variables that they have previously created in the IPUMS +web interface and saved to their user account. + +Because time use variables are a special type of variable, they need to be requested +using the ``time_use_variables`` argument specifically and cannot be added to +the ``variables`` argument list alongside standard variables. Below is an example +of an IPUMS ATUS extract that contains variables AGE and SEX as well as the system +time use variable BLS_PCARE: + +.. code:: python + + atus_extract = MicrodataExtract( + collection="atus", + samples=["at2016"], + variables=["AGE", "SEX"], + time_use_variables=["BLS_PCARE"], + description="An time use variable example" + ) + +Like other variables, time use variables can also be passed to +:class:`MicrodataExtract` as a list of +:class:`TimeUseVariable` objects. + +These objects are useful when requesting user-defined time use variables, as you must +specify the ``owner`` field, which must contain the email associated with your IPUMS account: + +.. code:: python + + atus_extract = MicrodataExtract( + collection="atus", + samples=["at2016"], + variables=["AGE", "SEX"], + time_use_variables=[ + TimeUseVariable(name="BLS_PCARE"), + TimeUseVariable(name="MY_CUSTOM_TUV", owner="newipumsuser@gmail.com") + ], + description="User-defined time use variable example" + ) + +IPUMS ATUS Sample Members +------------------------- + +Though time use information is only available for designated respondents in IPUMS ATUS, users +may also wish to include household members of these respondents and/or non-respondents in +their IPUMS ATUS extracts. These "sample members" can be included by using the ``sample_members`` +keyword argument. The example below includes both household members of ATUS respondents and +non-respondents alongside ATUS respondents (included by default). + +.. code-block:: python + + atus_extract = MicrodataExtract( + collection="atus", + samples=["at2016"], + variables=["AGE", "SEX"], + time_use_variables=[ + TimeUseVariable(name="BLS_PCARE"), + TimeUseVariable(name="MY_CUSTOM_TUV", owner="newipumsuser@gmail.com") + ], + sample_members={ + "include_non_respondents": True, + "include_household_members": True + }, + description="Sample members example" + ) + +Unsupported Extract Features +---------------------------- + +.. warning:: + + Not all features available through the IPUMS extract web UI are currently supported for extracts made via API. + For a list of supported and unsupported features for each IPUMS data collection, see + `the developer documentation `__. + This list will be updated as more features become available. + +.. rubric:: Notes + +.. [*] Note that IPUMS ATUS Eldercare records will be included in a hierarchical extract automatically if + Eldercare variables are selected. There is no API equivalent to the "include eldercare" checkbox + in the Extract Data Structure menu in the IPUMS ATUS web interface. + \ No newline at end of file diff --git a/docs/source/reading_data.rst b/docs/source/reading_data.rst index 5211363..27f7448 100644 --- a/docs/source/reading_data.rst +++ b/docs/source/reading_data.rst @@ -1,14 +1,14 @@ -.. reading_data +.. ipumspy documentation for reading microdata files .. currentmodule:: ipumspy Reading IPUMS Data ================== -Reading IPUMS Extracts ----------------------- +Reading IPUMS Microdata Extracts +-------------------------------- -Reading IPUMS data into a Pandas data frame using ``ipumspy`` requires a fixed-width or csv IPUMS extract data file and an IPUMS xml DDI file. +Reading IPUMS data into a pandas data frame using ipumspy requires a fixed-width or csv IPUMS extract data file and an IPUMS xml DDI file. To read a fixed-width rectangular IPUMS extract: @@ -35,7 +35,120 @@ The :meth:`readers.read_hierarchical_microdata()` method is for reading hierarch To get a single data frame for a hierarchical extract, set the ``as_dict`` flag in :meth:`readers.read_hierarchical_microdata()` to ``False``. +IPUMS Variable Metadata +*********************** + +Microdata extracts include a DDI codebook (XML) file which contains metadata about the contents of an +extract, including variables. + +Variable Descriptions +~~~~~~~~~~~~~~~~~~~~~ + +The :class:`VariableDescription` objects built from the DDI codebook +provide easy access to variable metadata. These can be returned using the :meth:`.get_variable_info()` method. + +Assuming that our data file contains the SEX variable, we could use the following: + +.. code:: python + + from ipumspy import readers + import pandas as pd + + # read ddi and data + ddi_codebook = readers.read_ipums_ddi(path/to/ddi/xml/file) + ipums_df = readers.read_microdata(ddi_codebook, path/to/data/file) + + # get VariableDescription for SEX + sex_info = ddi_codebook.get_variable_info("SEX") + + # see codes and labels for SEX + print(sex_info.codes) + #> {'Male': 1, 'Female': 2} + + # see variable description for SEX + print(sex_info.description) + #> SEX reports whether the person was male or female. + +You can use variable metadata to interpret the contents of your extract and make data processing +and analysis decision accordingly. + +More on Value Labels +~~~~~~~~~~~~~~~~~~~~ + +For categorical variables, you can filter your data using value labels instead of +numeric values. For example, the following code retains only the female respondents +in the ``ipums_df`` data frame from above, using the ``"Female"`` label: + +.. code:: python + + # retrieve the VaribleDescription for the variable SEX + sex_info = ddi_codebook.get_variable_info("SEX") + + # Filter to records where SEX is Female + women = ipums_df[ipums_df["SEX"] == sex_info.codes["Female"]] + +It is possible to filter both using labels as well as numeric values. +The following retains only women over the age of 16 in ``ipums_df``: + +.. code:: python + + adult_women = ipums_df[(ipums_df["SEX"] == sex_info.codes["Female"]) & + (ipums_df["AGE"] > 16)] + +.. _reading-aggregate-data: + +Reading IPUMS Aggregate Data Extracts +------------------------------------- + +By default, extracts for aggregate data projects are provided in csv format, +and therefore don't include DDI metadata files found in microdata extracts. While aggregate data +collections do provide codebook metadata files, they are in text format and are designed to be +human-readable rather used to parse the fixed-width files common for microdata projects. + +.. attention:: + Aggregate data codebook files contain the citation information for these collections; if you + use IPUMS data in a publication or report, please cite appropriately. + +When downloaded, IPUMS aggregate data extract files are compressed, and may include multiple files +in the provided zip archive. The easiest way to load data from these files is to use Python's +``zipfile`` module. + +For instance, to list the names of the files contained in an extract: + +.. code:: python + + from zipfile import ZipFile + import pandas as pd + + fname = "" + + # a list of individual file names from inside the .zip file + names = ZipFile(fname).namelist() + #> ['nhgis0025_csv/nhgis0025_ds120_1990_county_codebook.txt', 'nhgis0025_csv/nhgis0025_ds120_1990_county.csv'] + +You can use the names of the containing files to identify the files you wish to load. Here, we use +pandas to load the compressed csv file: + +.. code:: python + + # Read first data file in the extract + with ZipFile(fname) as z: + with z.open(names[1]) as f: + data = pd.read_csv(f) + +.. note:: + IPUMS NHGIS does allow you to request an extract in fixed-width format, but ipumspy does not + provide methods to parse these files as it does for Microdata because IPUMS NHGIS does not provide + the necessary DDI codebook. + +Shapefile data is delivered in a nested zipfile that can also be unpacked using the ``ZipFile`` module and read using third party libraries such as geopandas. + Reading Non-Extractable IPUMS Collections ----------------------------------------- -The `IPUMS YRBSS `__ and `IPUMS NYTS `__ data collections are not accessed through the IPUMS extract system, but are available for download in their entirety. ``ipumspy`` has functionality to download these datasets (:py:meth:`~noextract.download_noextract_data()`) and parse the yml format codebooks that come packaged with the ``ipumspy`` library (:py:meth:`~noextract.read_noextract_codebook()`). This codebook object can then be used to read the downloaded dataset into a Pandas data frame using :py:meth:`~readers.read_microdata()` as with other IPUMS datasets retrieved via the IPUMS extract system. \ No newline at end of file +The `IPUMS YRBSS `__ and `IPUMS NYTS `__ data +collections are not accessed through the IPUMS extract system, but are available for download in their entirety. ipumspy has +functionality to download these datasets (:py:meth:`~noextract.download_noextract_data()`) and parse the YAML format codebooks +that come packaged with the ipumspy library (:py:meth:`~noextract.read_noextract_codebook()`). This codebook object can +then be used to read the downloaded dataset into a pandas data frame using :py:meth:`~readers.read_microdata()` as with other +IPUMS microdata extracts retrieved via the IPUMS extract system. \ No newline at end of file diff --git a/docs/source/reference/api.rst b/docs/source/reference/api.rst index 99e088a..7327ad7 100644 --- a/docs/source/reference/api.rst +++ b/docs/source/reference/api.rst @@ -29,6 +29,7 @@ extract class. ipumspy.api.BaseExtract ipumspy.api.MicrodataExtract + ipumspy.api.AggregateDataExtract Other IPUMS Objects ------------------- @@ -43,9 +44,13 @@ Helpful data classes for defining IPUMS Extract objects. ipumspy.api.Variable ipumspy.api.Sample ipumspy.api.TimeUseVariable + ipumspy.api.Dataset + ipumspy.api.TimeSeriesTable + ipumspy.api.Shapefile Importing or Exporting Extract Definitions ------------------------------------------ + There are two convenience methods to transform ipumspy extract objects to dictionary objects and from dictonary objects to ipumspy extract objects. @@ -59,6 +64,20 @@ objects and from dictonary objects to ipumspy extract objects. ipumspy.api.extract.save_extract_as_json ipumspy.api.extract.define_extract_from_json +IPUMS Metadata +-------------- + +Use these classes to request IPUMS metadata via the IPUMS API. + +.. autosummary:: + :toctree: generated + :template: class.rst + :nosignatures: + + ipumspy.api.metadata.DatasetMetadata + ipumspy.api.metadata.DataTableMetadata + ipumspy.api.metadata.TimeSeriesTableMetadata + Exceptions ---------- diff --git a/docs/source/variables.rst b/docs/source/variables.rst deleted file mode 100644 index 800f571..0000000 --- a/docs/source/variables.rst +++ /dev/null @@ -1,66 +0,0 @@ -.. ipumspy documentation for working with IPUMS variables - -.. currentmodule:: ipumspy - -IPUMS Variables -=============== - -IPUMS Variable Objects ----------------------- - -A list of user-defined :class:`ipumspy.api.extract.Variable` objects can be passed to the IPUMS Extract classes to build extracts that take advantage of available IPUMS extract features. For more information on using IPUMS extract features in ``ipumspy``, see :ref:`Using Variable Objects to Include Extract Features` for more information. - -IPUMS Variable Metadata ------------------------ - -Currently, IPUMS metadata is not accessible via API and all variable information is pulled from an extract's DDI codebook. This codebook is created after the extract is submitted to the IPUMS extract system. - -Variable Descriptions -********************* - -The :class:`ipumspy.ddi.VariableDescription` objects built from the DDI codebook provide easy access to variable metadata. These can be returned using the :meth:`.get_variable_info()` method. - -.. code:: python - - from ipumspy import readers - - # read ddi and data - ddi_codebook = readers.read_ipums_ddi(path/to/ddi/xml/file) - ipums_df = readers.read_microdata(ddi_codebook, path/to/data/file) - - # get VariableDescription for SEX - sex_info = ddi_codebook.get_variable_info('SEX') - - # see codes and labels for SEX - print(sex_info.codes) - - # see variable description for SEX - print(sex_info.description) - -The above code results in the following:: - - # codes and labels - {'Male': 1, 'Female': 2} - - # description - 'SEX reports whether the person was male or female.' - -More on Value labels -******************** - -Users can filter on categorical variables using labels instead of numerical values -For example, the following code retains only the female respondents in ``ipums_df``. - -.. code:: python - - # retrieve the VaribleDescription for the variable SEX - sex_info = ddi_codebook.get_variable_info('SEX') - women = ipums_df[ipums_df['SEX'] == sex_info.codes['Female']] - -It is possible to filter on both categorical variables using labels and on numerical values. -The following retains only women over the age of 16 in ``ipums_df`` - -.. code:: python - - adult_women = ipums_df[(ipums_df['SEX'] == sex_info.codes['Female']) & - (ipums_df['AGE'] > 16)] \ No newline at end of file diff --git a/src/ipumspy/api/core.py b/src/ipumspy/api/core.py index 07cff3e..1ea4d5a 100644 --- a/src/ipumspy/api/core.py +++ b/src/ipumspy/api/core.py @@ -93,8 +93,9 @@ def __init__( Args: api_key: User's IPUMS API key base_url: IPUMS API url + api_version: IPUMS API version num_retries: number of times a request will be retried before - raising `TransientIpumsApiException` + raising ``TransientIpumsApiException`` session: requests session object """ @@ -207,8 +208,7 @@ def extract_status( then ``extract`` must be a ``BaseExtract`` Returns: - str: The status of the request. Valid statuses are: - 'queued', 'started', 'completed', 'failed', or 'not found' + str: The status of the request. Valid statuses are: 'queued', 'started', 'completed', 'failed', or 'not found' """ extract_id, collection = _extract_and_collection(extract, collection) @@ -250,7 +250,7 @@ def download_extract( extract data file. sas_command_file: Set to True to download the SAS command file with the extract data file. - R_command_file: Set to True to download the R command file with the + r_command_file: Set to True to download the R command file with the extract data file. """ extract_id, collection = _extract_and_collection(extract, collection) @@ -437,11 +437,12 @@ def get_extract_info( """ Returns details about a past IPUMS extract - extract: The extract to download. This extract must have been submitted. + Args: + extract: The extract to download. This extract must have been submitted. Alternatively, can be an extract id. If an extract id is provided, you must supply the collection name - collection: The name of the collection to pull the extract from. If None, - then ``extract`` must be a ``BaseExtract`` + collection: The name of the collection to pull the extract from. If None, + then ``extract`` must be a ``BaseExtract`` Returns: An IPUMS extract definition @@ -490,13 +491,14 @@ def extract_is_expired( collection: Optional[str] = None, ) -> bool: """ - Returns True if the IPUMS extract's files have been expired from the cache. + Returns ``True`` if the IPUMS extract's files have been expired from the cache. - extract: An extract object. This extract must have been submitted. - Alternatively, can be an extract id. If an extract id is provided, you - must supply the collection name - collection: The name of the collection to pull the extract from. If None, - then ``extract`` must be a ``BaseExtract`` + Args: + extract: An extract object. This extract must have been submitted. + Alternatively, can be an extract id. If an extract id is provided, you + must supply the collection name. + collection: The name of the collection to pull the extract from. If None, + then ``extract`` must be a ``BaseExtract``. """ extract_id, collection = _extract_and_collection(extract, collection) extract_definition = self.get_extract_info(extract_id, collection) diff --git a/src/ipumspy/api/extract.py b/src/ipumspy/api/extract.py index 17098f7..7358b80 100644 --- a/src/ipumspy/api/extract.py +++ b/src/ipumspy/api/extract.py @@ -50,7 +50,13 @@ def build(self): @dataclass class Variable(IpumsObject): """ - IPUMS variable object to include in an IPUMS extract object. + IPUMS variable object to include in a ``MicrodataExtract`` object. + + Args: + name: IPUMS variable name + case_selections: Case selection specifications + attached_characteristics: Attach characteristics specifications + data_quality_flags: Flag to include the variable's associated data quality flags if they exist """ name: str @@ -58,11 +64,11 @@ class Variable(IpumsObject): preselected: Optional[bool] = False """Whether the variable is preselected. Defaults to False.""" case_selections: Optional[Dict[str, List]] = field(default_factory=dict) - """Case selection specifications.""" + """Case selection specifications""" attached_characteristics: Optional[List[str]] = field(default_factory=list) - """Attach characteristics specifications.""" + """Attach characteristics specifications""" data_quality_flags: Optional[bool] = False - """Flag to include the variable's associated data quality flags if they exist.""" + """Flag to include the variable's associated data quality flags if they exist""" def __post_init__(self): self.name = self.name.upper() @@ -82,7 +88,10 @@ def build(self): @dataclass class Sample(IpumsObject): """ - IPUMS sample object to include in an IPUMS extract object. + IPUMS sample object to include in a ``MicrodataExtract`` object. + + Args: + id: IPUMS sample id """ id: str @@ -94,13 +103,22 @@ def __post_init__(self): self.id = self.id.lower() def build(self): + """Format Sample information for API Extract submission""" raise NotImplementedError @dataclass class TimeUseVariable(IpumsObject): + """ + IPUMS time use variable to include in a ``MicrodataExtract`` object. + + Args: + name: IPUMS time use variable name + owner: email address associated with your IPUMS account. Only required for user-defined time use variables. + """ + name: str - """IPUMS Time Use Variable name""" + """IPUMS time use variable name""" owner: Optional[str] = "" """email address associated with your IPUMS account. Only required for user-defined Time Use Variables.""" @@ -128,17 +146,24 @@ def build(self): @dataclass class Dataset(IpumsObject): """ - IPUMS Dataset object to include in an AggregateDataExtract object. + IPUMS dataset object to include in an ``AggregateDataExtract`` object. + + Args: + name: IPUMS dataset name + data_tables: IPUMS data tables to extract from this dataset + geog_levels: Geographic level(s) at which to obtain data for this dataset + years: Years for which to obtain data for this dataset (use ``['*']`` to select all years) + breakdown_values: Breakdown values to apply to this dataset """ name: str - """IPUMS NHGIS dataset name/id""" + """IPUMS dataset name""" data_tables: List[str] - """IPUMS NHGIS data tables to extract from this dataset""" + """IPUMS data tables to extract from this dataset""" geog_levels: List[str] """Geographic level(s) at which to obtain data for this dataset""" years: Optional[List[str]] = field(default_factory=list) - """Years for which to obtain data for this dataset""" + """Years for which to obtain data for this dataset (use ``['*']`` to select all years)""" breakdown_values: Optional[List[str]] = field(default_factory=list) """Breakdown values to apply to this dataset""" @@ -146,6 +171,7 @@ def __post_init__(self): self.years = [str(yr) for yr in self.years] def build(self): + """Format dataset information for API Extract submission""" built_dataset = self.__dict__.copy() # don't repeat the dataset name built_dataset.pop("name") @@ -161,11 +187,16 @@ def build(self): @dataclass class TimeSeriesTable(IpumsObject): """ - IPUMS TimeSeriesTable object to include in an AggregateDataExtract object. + IPUMS time series table object to include in an ``AggregateDataExtract`` object. + + Args: + name: IPUMS time series table name + geog_levels: Geographic level(s) at which to obtain data for this time series table. Use ``["*"]`` to select all years + years: Years for which to obtain data for this time series table """ name: str - """IPUMS NHGIS time series table name/id""" + """IPUMS time series table name""" geog_levels: List[str] # required parameter """Geographic level(s) at which to obtain data for this time series table""" years: Optional[Union[List[str], List[int]]] = field(default_factory=list) @@ -176,6 +207,7 @@ def __post_init__(self): self.years = [str(yr) for yr in self.years] def build(self): + """Format time series table information for API Extract submission""" built_tst = self.__dict__.copy() # don't repeat the time series table name built_tst.pop("name") @@ -189,13 +221,17 @@ def build(self): @dataclass class Shapefile(IpumsObject): """ - IPUMS Shapefile object to include in an AggregateDataExtract object. + IPUMS shapefile object to include in an ``AggregateDataExtract`` object. + + Args: + name: IPUMS shapefile name """ name: str - """IPUMS NHGIS shapefile name/id""" + """IPUMS shapefile name""" def build(self): + """Format shapefile information for API Extract submission""" raise NotImplementedError @@ -336,7 +372,7 @@ def build(self) -> Dict[str, Any]: @property def extract_id(self) -> int: """ - str:The extract id associated with this extract, assigned by the ``IpumsApiClient`` + str: The extract id associated with this extract, assigned by the ``IpumsApiClient`` Raises ``ValueError`` if the extract has no id number (probably because it has not be submitted to IPUMS) @@ -522,11 +558,14 @@ def __init__( sample_members: a dictionary of non-default sample members to include for Time Use collections where keys are strings indicating sample member type and values are boolean. This argument is only valid for IPUMS ATUS, MTUS, and AHTUS data collections. Valid keys include 'include_non_respondents' and 'include_household_members'. + case_select_who: indicates how to interpret any case selections included for variables in the extract. ``"individuals"`` + includes records for all individuals who match the specified case selections, while ``"households"`` + includes records for all members of each household that contains an individual who matches the specified case selections. """ super().__init__() self.collection_type = self.collection_type - """IPUMS Collection type""" + """IPUMS collection type""" self.collection = collection self.samples = self._validate_list_args(samples, Sample) self.variables = self._validate_list_args(variables, Variable) @@ -665,20 +704,20 @@ def __init__( **kwargs, ): """ - Class for defining an IPUMS NHGIS extract request. + Class for defining an extract request for an IPUMS aggregate data collection. Args: datasets: list of ``Dataset`` objects time_series_tables: list of ``TimeSeriesTable`` objects - shapefiles: list of shapefile IDs from IPUMS NHGIS + shapefiles: list of shapefile names description: short description of your extract data_format: desired format of the extract data file. One of ``"csv_no_header"``, ``"csv_header"``, or ``"fixed_width"``. geographic_extents: Geographic extents to use for all ``datasets`` in the extract definition (for instance, to - to obtain data within a particular state). Use ``*`` to select all available extents. Note that + to obtain data within a particular state). Use ``['*']`` to select all available extents. Note that not all geographic levels support extent selection. tst_layout: desired data layout for all ``time_series_tables`` in the extract definition. - One of ``"time_by_column_layout"``, ``"time_by_row_layout"``, or ``"time_by_file_layout"`` - breakdown_and_data_type_layout: desired layout of any `datasets` that have multiple data types or breakdown values. Either + One of ``"time_by_column_layout"`` (default), ``"time_by_row_layout"``, or ``"time_by_file_layout"``. + breakdown_and_data_type_layout: desired layout of any ``datasets`` that have multiple data types or breakdown values. Either ``"single_file"`` (default) or ``"separate files"`` """ @@ -686,6 +725,7 @@ def __init__( self.collection = collection self.collection_type = self.collection_type + """IPUMS collection type""" self.datasets = self._validate_list_args(datasets, Dataset) self.time_series_tables = self._validate_list_args( @@ -758,14 +798,14 @@ def build(self) -> Dict[str, Any]: def extract_from_dict(dct: Dict[str, Any]) -> Union[BaseExtract, List[BaseExtract]]: """ Convert an extract that is currently specified as a dictionary (usually from a file) - into a BaseExtract object. If multiple extracts are specified, return a - List[BaseExtract] objects. + into a ``BaseExtract`` object. If multiple extracts are specified, return a + ``List[BaseExtract]``. Args: dct: The dictionary specifying the extract(s) Returns: - The extract(s) specified by dct + The extract(s) specified by ``dct`` """ if "extracts" in dct: # We are returning several extracts @@ -798,7 +838,7 @@ def _make_snake_ext(ext_dict): def extract_to_dict(extract: Union[BaseExtract, List[BaseExtract]]) -> Dict[str, Any]: """ Convert an extract object to a dictionary (usually to write to a file). - If multiple extracts are specified, return a dict object. + If multiple extracts are specified, return a ``dict`` object. Args: extract: A submitted IPUMS extract object or list of submitted IPUMS extract objects diff --git a/src/ipumspy/api/metadata.py b/src/ipumspy/api/metadata.py index 2893736..22964aa 100644 --- a/src/ipumspy/api/metadata.py +++ b/src/ipumspy/api/metadata.py @@ -4,12 +4,12 @@ from dataclasses import dataclass from typing import Dict, List, Optional - +from abc import ABC @dataclass -class IpumsMetadata: +class IpumsMetadata(ABC): """ - Basic object to request and store metadata for an IPUMS resource + Basic class to request and store metadata for an arbitrary IPUMS resource """ _metadata_type = {} @@ -23,15 +23,17 @@ def __init_subclass__(cls, metadata_type: str, **kwargs): @dataclass class DatasetMetadata(IpumsMetadata, metadata_type="dataset"): """ - Object to request and store metadata for an IPUMS dataset + Class to request and store metadata for an IPUMS dataset + + Args: + collection: name of an IPUMS data collection + name: Name of an IPUMS dataset associated with the indicated collection """ collection: str """name of an IPUMS data collection""" name: str """IPUMS NHGIS dataset name""" - description: Optional[str] = None - """description of the dataset""" nhgis_id: Optional[str] = None """NHGIS ID used in NHGIS files to reference the dataset""" group: Optional[str] = None @@ -67,11 +69,14 @@ class DatasetMetadata(IpumsMetadata, metadata_type="dataset"): def __post_init__(self): self._path = f"metadata/datasets/{self.name}" - @dataclass class TimeSeriesTableMetadata(IpumsMetadata, metadata_type="time_series_table"): """ - Object to request and store metadata for an IPUMS time series table + Class to request and store metadata for an IPUMS time series table + + Args: + collection: IPUMS collection associated with this time series table + name: Name of the time series table for which to retrieve metadata """ collection: str @@ -104,7 +109,12 @@ def __post_init__(self): @dataclass class DataTableMetadata(IpumsMetadata, metadata_type="data_table"): """ - Object to request and store metadata for an IPUMS data table + Class to request and store metadata for an IPUMS data table + + Args: + collection: IPUMS collection associated with this data table + name: Name of the data table for which to retrieve metadata + dataset_name: Name of the dataset containing this data table """ collection: str diff --git a/src/ipumspy/ddi.py b/src/ipumspy/ddi.py index 4e95d24..4a87a4a 100644 --- a/src/ipumspy/ddi.py +++ b/src/ipumspy/ddi.py @@ -83,7 +83,7 @@ def numpy_type(self) -> type: @property def pandas_type(self) -> type: """ - The Pandas type of this variable. This supports the recently added nullable + The pandas type of this variable. This supports the recently added nullable pandas dtypes, and so the integer type is "Int64" and the string type is "string" (instead of "object") """ From b43b3f72830ddffd1eb89e9314634c44d39ceba9 Mon Sep 17 00:00:00 2001 From: Finn Roberts Date: Thu, 14 Nov 2024 16:30:48 -0500 Subject: [PATCH 23/35] Rename metadata class registry for clarity --- src/ipumspy/api/core.py | 2 +- src/ipumspy/api/metadata.py | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/ipumspy/api/core.py b/src/ipumspy/api/core.py index 1ea4d5a..5a06458 100644 --- a/src/ipumspy/api/core.py +++ b/src/ipumspy/api/core.py @@ -617,7 +617,7 @@ def get_metadata(self, obj: IpumsMetadata = None): ).json() metadata_resp = {_camel_to_snake(k): v for (k, v) in metadata_resp.items()} - metadata_class = IpumsMetadata._metadata_type[obj.metadata_type] + metadata_class = IpumsMetadata._metadata_classes[obj.metadata_type] metadata = metadata_class(collection=obj.collection, **metadata_resp) return metadata diff --git a/src/ipumspy/api/metadata.py b/src/ipumspy/api/metadata.py index 22964aa..e2e87dd 100644 --- a/src/ipumspy/api/metadata.py +++ b/src/ipumspy/api/metadata.py @@ -6,18 +6,19 @@ from typing import Dict, List, Optional from abc import ABC + @dataclass class IpumsMetadata(ABC): """ Basic class to request and store metadata for an arbitrary IPUMS resource """ - _metadata_type = {} + _metadata_classes = {} def __init_subclass__(cls, metadata_type: str, **kwargs): super().__init_subclass__(**kwargs) cls.metadata_type = metadata_type - IpumsMetadata._metadata_type[metadata_type] = cls + IpumsMetadata._metadata_classes[metadata_type] = cls @dataclass @@ -69,6 +70,7 @@ class DatasetMetadata(IpumsMetadata, metadata_type="dataset"): def __post_init__(self): self._path = f"metadata/datasets/{self.name}" + @dataclass class TimeSeriesTableMetadata(IpumsMetadata, metadata_type="time_series_table"): """ From 2e351647a5cca017540e8e42e88b11b62fb29e1f Mon Sep 17 00:00:00 2001 From: Finn Roberts Date: Fri, 15 Nov 2024 16:09:44 -0500 Subject: [PATCH 24/35] Alternate metadata API --- src/ipumspy/api/metadata.py | 73 +++++++++++++++++++++++-------------- 1 file changed, 46 insertions(+), 27 deletions(-) diff --git a/src/ipumspy/api/metadata.py b/src/ipumspy/api/metadata.py index e2e87dd..9e56a20 100644 --- a/src/ipumspy/api/metadata.py +++ b/src/ipumspy/api/metadata.py @@ -2,12 +2,12 @@ Classes for requesting IPUMS metadata via the IPUMS API """ -from dataclasses import dataclass +from dataclasses import dataclass, field from typing import Dict, List, Optional from abc import ABC -@dataclass +@dataclass(init=False) class IpumsMetadata(ABC): """ Basic class to request and store metadata for an arbitrary IPUMS resource @@ -15,13 +15,16 @@ class IpumsMetadata(ABC): _metadata_classes = {} + def __init__(self, **kwargs): + pass + def __init_subclass__(cls, metadata_type: str, **kwargs): super().__init_subclass__(**kwargs) cls.metadata_type = metadata_type IpumsMetadata._metadata_classes[metadata_type] = cls -@dataclass +@dataclass(init=False) class DatasetMetadata(IpumsMetadata, metadata_type="dataset"): """ Class to request and store metadata for an IPUMS dataset @@ -35,43 +38,48 @@ class DatasetMetadata(IpumsMetadata, metadata_type="dataset"): """name of an IPUMS data collection""" name: str """IPUMS NHGIS dataset name""" - nhgis_id: Optional[str] = None + nhgis_id: Optional[str] = field(default=None, init=False) """NHGIS ID used in NHGIS files to reference the dataset""" - group: Optional[str] = None + group: Optional[str] = field(default=None, init=False) """group of datasets to which the dataset belongs""" - sequence: Optional[str] = None + sequence: Optional[str] = field(default=None, init=False) """order in which the dataset will appear in the metadata API and extracts""" - has_multiple_data_types: Optional[bool] = None + has_multiple_data_types: Optional[bool] = field(default=None, init=False) """ logical indicating whether multiple data types exist for the dataset (for example, ACS datasets include both estimates and margins of error) """ - data_tables: Optional[List[Dict]] = None + data_tables: Optional[List[Dict]] = field(default=None, init=False) """ dictionary containing names, codes, and descriptions for all data tables available for the dataset """ - geog_levels: Optional[List[Dict]] = None + geog_levels: Optional[List[Dict]] = field(default=None, init=False) """ dictionary containing names, descriptions, and extent information for the geographic levels available for the dataset """ - geographic_instances: Optional[List[Dict]] = None + geographic_instances: Optional[List[Dict]] = field(default=None, init=False) """ dictionary containing names and descriptions for all valid geographic extents for the dataset """ - breakdowns: Optional[List[Dict]] = None + breakdowns: Optional[List[Dict]] = field(default=None, init=False) """ dictionary containing names, types, descriptions, and breakdown values for all breakdowns available for the dataset. """ - def __post_init__(self): + def __init__(self, collection, name, **kwargs): + self.collection = collection + self.name = name self._path = f"metadata/datasets/{self.name}" + for key, value in kwargs.items(): + setattr(self, key, value) + -@dataclass +@dataclass(init=False) class TimeSeriesTableMetadata(IpumsMetadata, metadata_type="time_series_table"): """ Class to request and store metadata for an IPUMS time series table @@ -85,9 +93,9 @@ class TimeSeriesTableMetadata(IpumsMetadata, metadata_type="time_series_table"): """name of an IPUMS data collection""" name: str """IPUMS NHGIS time series table name""" - description: Optional[str] = None + description: Optional[str] = field(default=None, init=False) """description of the time series table""" - geographic_integration: Optional[str] = None + geographic_integration: Optional[str] = field(default=None, init=False) """ The method by which the time series table aligns geographic units across time. Nominal integration indicates that geographic units @@ -95,20 +103,25 @@ class TimeSeriesTableMetadata(IpumsMetadata, metadata_type="time_series_table"): Standardized integration indicates that data from multiple time points are standardized to the indicated year's census units """ - sequence: Optional[str] = None + sequence: Optional[str] = field(default=None, init=False) """order in which the time series table will appear in the metadata API and extracts""" - time_series: Optional[List[Dict]] = None + time_series: Optional[List[Dict]] = field(default=None, init=False) """dictionary containing names and descriptions for the individual time series available for the time series table""" - years: Optional[List[Dict]] = None + years: Optional[List[Dict]] = field(default=None, init=False) """dictionary containing information on the available data years for the time series table""" - geog_levels: Optional[List[Dict]] = None + geog_levels: Optional[List[Dict]] = field(default=None, init=False) """dictionary containing names and descriptions for the geographic levels available for the time series table""" - def __post_init__(self): + def __init__(self, collection, name, **kwargs): + self.collection = collection + self.name = name self._path = f"metadata/time_series_tables/{self.name}" + for key, value in kwargs.items(): + setattr(self, key, value) -@dataclass + +@dataclass(init=False) class DataTableMetadata(IpumsMetadata, metadata_type="data_table"): """ Class to request and store metadata for an IPUMS data table @@ -125,19 +138,25 @@ class DataTableMetadata(IpumsMetadata, metadata_type="data_table"): """IPUMS data table name""" dataset_name: str """name of the dataset to which this data table belongs""" - description: Optional[str] = None + description: Optional[str] = field(default=None, init=False) """description of the data table""" - universe: Optional[str] = None + universe: Optional[str] = field(default=None, init=False) """the statistical population measured by this data table""" - nhgis_code: Optional[str] = None + nhgis_code: Optional[str] = field(default=None, init=False) """ the code identifying the data table in the extract. Variables in the extract data will include column names prefixed with this code """ - sequence: Optional[str] = None + sequence: Optional[str] = field(default=None, init=False) """order in which the data table will appear in the metadata API and extracts""" - variables: Optional[List[Dict]] = None + variables: Optional[List[Dict]] = field(default=None, init=False) """dictionary containing variable descriptions and codes for the variables included in the data table""" - def __post_init__(self): + def __init__(self, collection, name, dataset_name, **kwargs): + self.collection = collection + self.name = name + self.dataset_name = dataset_name self._path = f"metadata/datasets/{self.dataset_name}/data_tables/{self.name}" + + for key, value in kwargs.items(): + setattr(self, key, value) From 748ffc81e0e2ac279f181c46a26c9c77ad1a5321 Mon Sep 17 00:00:00 2001 From: renae-r Date: Mon, 25 Nov 2024 15:04:25 -0600 Subject: [PATCH 25/35] updated metadata dataclasses --- src/ipumspy/api/core.py | 6 ++-- src/ipumspy/api/metadata.py | 60 ++++++++++++++++--------------------- 2 files changed, 27 insertions(+), 39 deletions(-) diff --git a/src/ipumspy/api/core.py b/src/ipumspy/api/core.py index 5a06458..ac1889c 100644 --- a/src/ipumspy/api/core.py +++ b/src/ipumspy/api/core.py @@ -617,7 +617,5 @@ def get_metadata(self, obj: IpumsMetadata = None): ).json() metadata_resp = {_camel_to_snake(k): v for (k, v) in metadata_resp.items()} - metadata_class = IpumsMetadata._metadata_classes[obj.metadata_type] - metadata = metadata_class(collection=obj.collection, **metadata_resp) - - return metadata + obj.populate(metadata_resp) + return obj diff --git a/src/ipumspy/api/metadata.py b/src/ipumspy/api/metadata.py index 9e56a20..6d3d7a0 100644 --- a/src/ipumspy/api/metadata.py +++ b/src/ipumspy/api/metadata.py @@ -7,25 +7,28 @@ from abc import ABC -@dataclass(init=False) +@dataclass class IpumsMetadata(ABC): """ Basic class to request and store metadata for an arbitrary IPUMS resource """ + + def populate(self, metadata_response_dict: dict): + """ + Update IpumsMetadata objects with attributes from API response - _metadata_classes = {} + Args: + metadata_response_dict: json response from IPUMS metadata API + """ + for attribute in metadata_response_dict.keys(): + if hasattr(self, attribute): + setattr(self, attribute, metadata_response_dict[attribute]) + else: + raise KeyError(f"{type(self).__name__} has no attribute '{attribute}'.") - def __init__(self, **kwargs): - pass - def __init_subclass__(cls, metadata_type: str, **kwargs): - super().__init_subclass__(**kwargs) - cls.metadata_type = metadata_type - IpumsMetadata._metadata_classes[metadata_type] = cls - - -@dataclass(init=False) -class DatasetMetadata(IpumsMetadata, metadata_type="dataset"): +@dataclass +class DatasetMetadata(IpumsMetadata): """ Class to request and store metadata for an IPUMS dataset @@ -42,6 +45,8 @@ class DatasetMetadata(IpumsMetadata, metadata_type="dataset"): """NHGIS ID used in NHGIS files to reference the dataset""" group: Optional[str] = field(default=None, init=False) """group of datasets to which the dataset belongs""" + description: Optional[str] = field(default=None, init=False) + """description of the dataset from IPUMS""" sequence: Optional[str] = field(default=None, init=False) """order in which the dataset will appear in the metadata API and extracts""" has_multiple_data_types: Optional[bool] = field(default=None, init=False) @@ -70,17 +75,13 @@ class DatasetMetadata(IpumsMetadata, metadata_type="dataset"): available for the dataset. """ - def __init__(self, collection, name, **kwargs): - self.collection = collection - self.name = name + def __post_init__(self): self._path = f"metadata/datasets/{self.name}" - for key, value in kwargs.items(): - setattr(self, key, value) -@dataclass(init=False) -class TimeSeriesTableMetadata(IpumsMetadata, metadata_type="time_series_table"): +@dataclass +class TimeSeriesTableMetadata(IpumsMetadata): """ Class to request and store metadata for an IPUMS time series table @@ -112,17 +113,12 @@ class TimeSeriesTableMetadata(IpumsMetadata, metadata_type="time_series_table"): geog_levels: Optional[List[Dict]] = field(default=None, init=False) """dictionary containing names and descriptions for the geographic levels available for the time series table""" - def __init__(self, collection, name, **kwargs): - self.collection = collection - self.name = name + def __post_init__(self): self._path = f"metadata/time_series_tables/{self.name}" - for key, value in kwargs.items(): - setattr(self, key, value) - -@dataclass(init=False) -class DataTableMetadata(IpumsMetadata, metadata_type="data_table"): +@dataclass +class DataTableMetadata(IpumsMetadata): """ Class to request and store metadata for an IPUMS data table @@ -152,11 +148,5 @@ class DataTableMetadata(IpumsMetadata, metadata_type="data_table"): variables: Optional[List[Dict]] = field(default=None, init=False) """dictionary containing variable descriptions and codes for the variables included in the data table""" - def __init__(self, collection, name, dataset_name, **kwargs): - self.collection = collection - self.name = name - self.dataset_name = dataset_name - self._path = f"metadata/datasets/{self.dataset_name}/data_tables/{self.name}" - - for key, value in kwargs.items(): - setattr(self, key, value) + def __post_init__(self): + self._path = self._path = f"metadata/datasets/{self.dataset_name}/data_tables/{self.name}" \ No newline at end of file From 084067f43f41c49998ff663d52f44a88cbd0aa34 Mon Sep 17 00:00:00 2001 From: renae-r Date: Mon, 25 Nov 2024 15:45:49 -0600 Subject: [PATCH 26/35] add collection validation and give useful error messages --- src/ipumspy/api/metadata.py | 46 +++++++++++++++++++++++++++++-------- tests/test_metadata.py | 6 +++++ 2 files changed, 43 insertions(+), 9 deletions(-) diff --git a/src/ipumspy/api/metadata.py b/src/ipumspy/api/metadata.py index 6d3d7a0..3977fa8 100644 --- a/src/ipumspy/api/metadata.py +++ b/src/ipumspy/api/metadata.py @@ -4,7 +4,7 @@ from dataclasses import dataclass, field from typing import Dict, List, Optional -from abc import ABC +from abc import ABC, abstractmethod @dataclass @@ -25,6 +25,17 @@ def populate(self, metadata_response_dict: dict): setattr(self, attribute, metadata_response_dict[attribute]) else: raise KeyError(f"{type(self).__name__} has no attribute '{attribute}'.") + + @property + @abstractmethod + def supported_collections(self): + pass + + def _validate_collection(self): + if self.collection not in self.supported_collections: + raise ValueError(f"{type(self).__name__} is not a valid metadata type for the {self.collection} collection.") + + @dataclass @@ -34,15 +45,15 @@ class DatasetMetadata(IpumsMetadata): Args: collection: name of an IPUMS data collection - name: Name of an IPUMS dataset associated with the indicated collection + name: Name of an IPUMS dataset included in the collection """ collection: str - """name of an IPUMS data collection""" + """name of an IPUMS data collection. Currently only available for IPUMS NHGIS""" name: str - """IPUMS NHGIS dataset name""" + """IPUMS dataset name from an IPUMS aggregate data collection""" nhgis_id: Optional[str] = field(default=None, init=False) - """NHGIS ID used in NHGIS files to reference the dataset""" + """ID used in IPUMS files to reference the dataset""" group: Optional[str] = field(default=None, init=False) """group of datasets to which the dataset belongs""" description: Optional[str] = field(default=None, init=False) @@ -77,6 +88,11 @@ class DatasetMetadata(IpumsMetadata): def __post_init__(self): self._path = f"metadata/datasets/{self.name}" + self._validate_collection() + + @property + def supported_collections(self): + return ["nhgis"] @@ -86,14 +102,14 @@ class TimeSeriesTableMetadata(IpumsMetadata): Class to request and store metadata for an IPUMS time series table Args: - collection: IPUMS collection associated with this time series table + collection: IPUMS collection that contains time series tables name: Name of the time series table for which to retrieve metadata """ collection: str - """name of an IPUMS data collection""" + """name of an IPUMS data collection. Only available for IPUMS NHGIS""" name: str - """IPUMS NHGIS time series table name""" + """IPUMS time series table name from an IPUMS aggregate data collection.""" description: Optional[str] = field(default=None, init=False) """description of the time series table""" geographic_integration: Optional[str] = field(default=None, init=False) @@ -115,6 +131,13 @@ class TimeSeriesTableMetadata(IpumsMetadata): def __post_init__(self): self._path = f"metadata/time_series_tables/{self.name}" + self._validate_collection() + + @property + def supported_collections(self): + return ["nhgis"] + + @dataclass @@ -149,4 +172,9 @@ class DataTableMetadata(IpumsMetadata): """dictionary containing variable descriptions and codes for the variables included in the data table""" def __post_init__(self): - self._path = self._path = f"metadata/datasets/{self.dataset_name}/data_tables/{self.name}" \ No newline at end of file + self._path = self._path = f"metadata/datasets/{self.dataset_name}/data_tables/{self.name}" + self._validate_collection() + + @property + def supported_collections(self): + return ["nhgis"] diff --git a/tests/test_metadata.py b/tests/test_metadata.py index bb57f88..96b4db0 100644 --- a/tests/test_metadata.py +++ b/tests/test_metadata.py @@ -62,3 +62,9 @@ def test_get_metadata(live_api_client: IpumsApiClient): assert len(ds.data_tables) == 100 assert len(dt.variables) == 49 + + +def test_collection_validity(): + with pytest.raises(ValueError) as exc_info: + ds = DatasetMetadata("usa", "1990_STF1") + assert exc_info.value.args[0] == "DatasetMetadata is not a valid metadata type for the usa collection." From 0741ee6d7461da5775c62a58ee3995c583d6c075 Mon Sep 17 00:00:00 2001 From: Finn Roberts Date: Fri, 6 Dec 2024 16:10:48 -0500 Subject: [PATCH 27/35] Metadata doc updates --- docs/source/ipums_api/index.rst | 89 +++++++++++++++++-- .../ipums_api/ipums_api_aggregate/index.rst | 72 +++++++++++++-- src/ipumspy/api/core.py | 14 +-- src/ipumspy/api/metadata.py | 76 ++++++++-------- 4 files changed, 192 insertions(+), 59 deletions(-) diff --git a/docs/source/ipums_api/index.rst b/docs/source/ipums_api/index.rst index b4070b2..9de93ee 100644 --- a/docs/source/ipums_api/index.rst +++ b/docs/source/ipums_api/index.rst @@ -89,10 +89,6 @@ available features for all collections currently supported by the API: - **X** - **X** -.. tip:: - While the IPUMS API does not yet provide comprehensive metadata for microdata collections, you can use - :py:meth:`.get_all_sample_info` to retrieve a dictionary of available sample IDs. - Note that ipumspy may not necessarily support all the functionality currently supported by the IPUMS API. See the `API documentation `__ for more information about its latest features. @@ -141,12 +137,87 @@ AGE, SEX, RACE, STATEFIP, and MARST variables from the 2018 and 2019 American Co ) .. seealso:: - The available extract definition options vary across collections. See the collection-specific - documentation for details about specifying more complex extracts for each type: - - :doc:`Microdata extracts` - Information on microdata extract parameters + The available extract definition options vary across collections. See the + :doc:`microdata extracts` and :doc:`aggregate data extracts` + pages for more information about the available extract parameters for each type. + +.. _ipums-metadata: + +IPUMS Metadata +************** + +You can use the IPUMS API metadata endpoints to identify the codes you can use to include +particular data sources in your extract request. + +The IPUMS API provides access to two different types of metadata. The first provides a listing of all +available data sources of a given type (see the :ref:`table ` below for supported types). +These records can be accessed with :py:meth:`.get_metadata_catalog`. + +This method returns a generator of metadata pages, allowing you to iterate through and search for +particular data sources. For instance, to identify all available IPUMS NHGIS data tables that +contain data referring to "Urban Population", we could do the following: + +.. code:: python + + urb_dts = [] + + # Identify all data tables referring to "Urban Population" + for page in ipums.get_metadata_catalog("nhgis", metadata_type="data_tables"): + for dt in page["data"]: + if "Urban Population" in dt["description"]: + urb_dts.append(dt) + +The IPUMS API also provides access to detailed metadata about individual data sources. Request +this metadata by using an ``IpumsMetadata`` object to indicate the individual data source +for which to retrieve metadata. For instance, to request metadata for IPUMS NHGIS time series table "A00": + +.. code:: python + + tst = TimeSeriesTableMetadata("nhgis", "A00") + +Submit the request to the IPUMS API with :py:meth:`.get_metadata`. The returned object will contain the +metadata obtained for the requested data source: + +.. code:: python + + ipums.get_metadata(tst) + + tst.description + #> 'Total Population' + +The following table summarizes the currently available metadata endpoints: + +.. _metadata support table: + +.. list-table:: Supported metadata endpoints + :widths: 2 3 5 + :header-rows: 1 + :align: center + + * - Metadata type + - Supported collections + - Detailed metadata class analog + * - ``datasets`` + - IPUMS NHGIS + - :py:class:`~ipumspy.api.metadata.DatasetMetadata` + * - ``data_tables`` + - IPUMS NHGIS + - :py:class:`~ipumspy.api.metadata.DataTableMetadata` + * - ``time_series_tables`` + - IPUMS NHGIS + - :py:class:`~ipumspy.api.metadata.TimeSeriesTableMetadata` + * - ``shapefiles`` + - IPUMS NHGIS + - + * - ``samples`` + - Microdata collections + - + +.. note:: + Currently, comprehensive IPUMS API metadata is only available for IPUMS NHGIS. + For microdata collections, only sample information is available. You can also obtain a dictionary + of sample codes with :py:meth:`.get_all_sample_info`. - :doc:`Aggregate data extracts` - Information on aggregate data extract parameters .. _submit-extract: diff --git a/docs/source/ipums_api/ipums_api_aggregate/index.rst b/docs/source/ipums_api/ipums_api_aggregate/index.rst index 85a4ec4..688d940 100644 --- a/docs/source/ipums_api/ipums_api_aggregate/index.rst +++ b/docs/source/ipums_api/ipums_api_aggregate/index.rst @@ -52,8 +52,6 @@ After instantiation, an ``AggregateDataExtract`` object can be You can also browse metadata interactively through the `NHGIS Data Finder `_. - *TODO: cross-link to metadata docs when available* - Datasets + Data Tables ---------------------- @@ -124,8 +122,37 @@ For datasets with multiple breakdowns or data types (e.g., the American Communit and margins of error), you can request that the data for each be provided in separate files or together in a single file using the ``breakdown_and_data_type_layout`` argument. +Dataset + Data Table Metadata ++++++++++++++++++++++++++++++ + +Use the :class:`DatasetMetadata ` class to browse the available +specification options for a particular dataset and identify the codes to use when +requesting data from the API: + +.. code:: python + + from ipumspy import IpumsApiClient, DatasetMetadata + + ipums = IpumsApiClient(os.environ.get("IPUMS_API_KEY")) + + ds = ipums.get_metadata(DatasetMetadata("nhgis", "2000_SF1a")) + +The returned object will contain the metadata for the requested dataset. For example: + +.. code:: python + + # Description of the dataset + ds.description + + # Dictionary of data table codes for this dataset + ds.data_tables + + # etc... + +You can also request metadata for individual data tables using the same workflow with the :class:`DataTableMetadata ` class. + Geographic Extent Selection -*************************** ++++++++++++++++++++++++++++ When working with small geographies it can be computationally intensive to work with nationwide data. To avoid this problem, you can request data from a specific geographic area @@ -147,13 +174,14 @@ a trailing 0): geographic_extents=["010", "050"] ) +.. tip:: + You can see available extent selection API codes, if any, in the ``geographic_instances`` attribute of + a submitted :class:`DatasetMetadata ` object. + Note that extent selection is *not* a dataset-specific parameter. That is, the selected extents are applied to all datasets in the extract. It is not possible to request different extents for different datasets in a single extract. -.. note:: - Currently, NHGIS only supports state-level extent selection for census blocks and block groups. - Time Series Tables ------------------ @@ -206,6 +234,9 @@ into separate files (by default, time is arranged across columns). tst_layout="time_by_row_layout", ) +As with datasets and data tables, you can request metadata about the available specification options +for a specific time series table using the :class:`TimeSeriesTableMetadata ` class. + Shapefiles ---------- @@ -272,3 +303,32 @@ fixed-width format if so desired. Note that unlike for microdata projects, NHGIS not provide DDI codebook files (in XML format), which allow ipumspy to parse microdata fixed-width files. Thus, loading an NHGIS fixed width file will require manual work to parse the file correctly. + +Supplemental Data +----------------- + +IPUMS NHGIS also provides some data products via direct download, without the need to create +an extract request. These sources are available via the IPUMS API. However, since you access +these files directly, you must know a file's URL before you can download it. + +Many NHGIS supplemental data files can be found under the "Supplemental Data" heading on the left side of the +`NHGIS homepage `_. See the IPUMS +`developer documentation page `_ +for all supported supplemental data endpoints and advice on how to convert file URLs found on the website into +acceptable API request URLs. + +Once you've identified a file's location, you can use the :py:meth:`.get` method to download it. For +instance, to download a state-level NHGIS crosswalk file, we could use the following: + +.. code:: python + + file_name = "nhgis_blk2010_blk2020_10.zip" + url = f"{ipums.base_url}/supplemental-data/nhgis/crosswalks/nhgis_blk2010_blk2020_state/{file_name}" + + download_path = "" + + with ipums.get(url, stream=True) as response: + with open(download_path, "wb") as outfile: + for chunk in response.iter_content(chunk_size=8192): + outfile.write(chunk) + diff --git a/src/ipumspy/api/core.py b/src/ipumspy/api/core.py index ac1889c..3204cad 100644 --- a/src/ipumspy/api/core.py +++ b/src/ipumspy/api/core.py @@ -583,12 +583,12 @@ def get_metadata_catalog( self, collection: str, metadata_type: str, page_size: Optional[int] = 2500 ) -> Generator[Dict, None, None]: """ - Retrieve a catalog containing a summary of all resources of a given type for a given IPUMS collection + Retrieve a catalog containing a summary of all resources of a given type for a given IPUMS collection. Args: - collection: the name of the IPUMS collection to retrieve metadata for - metadata_type: name of the type of metadata to retrieve for this collection - page_size: The number of items to return per page. Default to maximum page size, 2500. + collection: The name of the IPUMS collection to retrieve metadata for + metadata_type: Name of the type of metadata to retrieve for this collection + page_size: Number of items to return per page. Defaults to maximum page size, 2500. Yields: An iterator of metadata pages @@ -596,12 +596,12 @@ def get_metadata_catalog( yield from self._get_pages(collection, f"metadata/{metadata_type}", page_size) - def get_metadata(self, obj: IpumsMetadata = None): + def get_metadata(self, obj: IpumsMetadata): """ - Retrieve detailed metadata for a specific IPUMS resource + Retrieve detailed metadata for a specific IPUMS resource. Args: - obj: metadata object specifying the IPUMS resource for which to retrieve metadata + obj: Metadata object specifying the IPUMS resource for which to retrieve metadata Returns: An object of the same class as ``obj`` with attributes containing the metadata receieved from the API diff --git a/src/ipumspy/api/metadata.py b/src/ipumspy/api/metadata.py index 3977fa8..c4c4b53 100644 --- a/src/ipumspy/api/metadata.py +++ b/src/ipumspy/api/metadata.py @@ -10,15 +10,16 @@ @dataclass class IpumsMetadata(ABC): """ - Basic class to request and store metadata for an arbitrary IPUMS resource + Class to request and store metadata for an arbitrary IPUMS resource. Use a subclass to request + metadata for a particular type of resource. """ def populate(self, metadata_response_dict: dict): """ - Update IpumsMetadata objects with attributes from API response + Update IpumsMetadata objects with attributes from API response. Args: - metadata_response_dict: json response from IPUMS metadata API + metadata_response_dict: JSON response from IPUMS metadata API """ for attribute in metadata_response_dict.keys(): if hasattr(self, attribute): @@ -29,6 +30,9 @@ def populate(self, metadata_response_dict: dict): @property @abstractmethod def supported_collections(self): + """ + Collections that support this metadata class + """ pass def _validate_collection(self): @@ -49,41 +53,41 @@ class DatasetMetadata(IpumsMetadata): """ collection: str - """name of an IPUMS data collection. Currently only available for IPUMS NHGIS""" + """Name of an IPUMS data collection""" name: str - """IPUMS dataset name from an IPUMS aggregate data collection""" + """IPUMS dataset name""" nhgis_id: Optional[str] = field(default=None, init=False) """ID used in IPUMS files to reference the dataset""" group: Optional[str] = field(default=None, init=False) - """group of datasets to which the dataset belongs""" + """Group of datasets to which the dataset belongs""" description: Optional[str] = field(default=None, init=False) - """description of the dataset from IPUMS""" + """Description of the dataset from IPUMS""" sequence: Optional[str] = field(default=None, init=False) - """order in which the dataset will appear in the metadata API and extracts""" + """Order in which the dataset will appear in the metadata API and extracts""" has_multiple_data_types: Optional[bool] = field(default=None, init=False) """ - logical indicating whether multiple data types exist for the dataset - (for example, ACS datasets include both estimates and margins of error) + Logical indicating whether multiple data types exist for the dataset + (e.g., estimates and margins of error) """ data_tables: Optional[List[Dict]] = field(default=None, init=False) """ - dictionary containing names, codes, and descriptions for all data tables + Dictionary containing names, codes, and descriptions for all data tables available for the dataset """ geog_levels: Optional[List[Dict]] = field(default=None, init=False) """ - dictionary containing names, descriptions, and extent information for the geographic + Dictionary containing names, descriptions, and extent information for the geographic levels available for the dataset """ geographic_instances: Optional[List[Dict]] = field(default=None, init=False) """ - dictionary containing names and descriptions for all valid geographic extents + Dictionary containing names and descriptions for all valid geographic extents for the dataset """ breakdowns: Optional[List[Dict]] = field(default=None, init=False) """ - dictionary containing names, types, descriptions, and breakdown values for all breakdowns - available for the dataset. + Dictionary containing names, types, descriptions, and breakdown values for all breakdowns + available for the dataset """ def __post_init__(self): @@ -99,7 +103,7 @@ def supported_collections(self): @dataclass class TimeSeriesTableMetadata(IpumsMetadata): """ - Class to request and store metadata for an IPUMS time series table + Class to request and store metadata for an IPUMS time series table. Args: collection: IPUMS collection that contains time series tables @@ -107,27 +111,25 @@ class TimeSeriesTableMetadata(IpumsMetadata): """ collection: str - """name of an IPUMS data collection. Only available for IPUMS NHGIS""" + """Name of an IPUMS data collection""" name: str - """IPUMS time series table name from an IPUMS aggregate data collection.""" + """IPUMS time series table name""" description: Optional[str] = field(default=None, init=False) - """description of the time series table""" + """Description of the time series table""" geographic_integration: Optional[str] = field(default=None, init=False) - """ - The method by which the time series table aligns geographic units - across time. Nominal integration indicates that geographic units - are aligned by name (disregarding changes in unit boundaries). - Standardized integration indicates that data from multiple time - points are standardized to the indicated year's census units + """The method by which the time series table aligns geographic units across time + ("nominal" integration aligns units by name, disregarding changes + in unit boundaries; "standardized" integration provides data from multiple + times for a single census's geographic units) """ sequence: Optional[str] = field(default=None, init=False) - """order in which the time series table will appear in the metadata API and extracts""" + """Order in which the time series table will appear in the metadata API and extracts""" time_series: Optional[List[Dict]] = field(default=None, init=False) - """dictionary containing names and descriptions for the individual time series available for the time series table""" + """Dictionary containing names and descriptions for the individual time series available for the time series table""" years: Optional[List[Dict]] = field(default=None, init=False) - """dictionary containing information on the available data years for the time series table""" + """Dictionary containing information on the available data years for the time series table""" geog_levels: Optional[List[Dict]] = field(default=None, init=False) - """dictionary containing names and descriptions for the geographic levels available for the time series table""" + """Dictionary containing names and descriptions for the geographic levels available for the time series table""" def __post_init__(self): self._path = f"metadata/time_series_tables/{self.name}" @@ -152,24 +154,24 @@ class DataTableMetadata(IpumsMetadata): """ collection: str - """name of an IPUMS data collection""" + """Name of an IPUMS data collection""" name: str """IPUMS data table name""" dataset_name: str - """name of the dataset to which this data table belongs""" + """Name of the dataset to which this data table belongs""" description: Optional[str] = field(default=None, init=False) - """description of the data table""" + """Description of the data table""" universe: Optional[str] = field(default=None, init=False) - """the statistical population measured by this data table""" + """The statistical population measured by this data table""" nhgis_code: Optional[str] = field(default=None, init=False) """ - the code identifying the data table in the extract. Variables in the extract - data will include column names prefixed with this code + The code identifying the data table in the extract (variables in the + extract data will include column names prefixed with this code) """ sequence: Optional[str] = field(default=None, init=False) - """order in which the data table will appear in the metadata API and extracts""" + """Order in which the data table will appear in the metadata API and extracts""" variables: Optional[List[Dict]] = field(default=None, init=False) - """dictionary containing variable descriptions and codes for the variables included in the data table""" + """Dictionary containing variable descriptions and codes for the variables included in the data table""" def __post_init__(self): self._path = self._path = f"metadata/datasets/{self.dataset_name}/data_tables/{self.name}" From 1a121972d47970020cf4fb8060d154a9deb55156 Mon Sep 17 00:00:00 2001 From: Finn Roberts Date: Fri, 6 Dec 2024 16:27:13 -0500 Subject: [PATCH 28/35] Add rate limit exception class --- src/ipumspy/api/core.py | 3 +++ src/ipumspy/api/exceptions.py | 3 +++ 2 files changed, 6 insertions(+) diff --git a/src/ipumspy/api/core.py b/src/ipumspy/api/core.py index 3204cad..9a9dc9e 100644 --- a/src/ipumspy/api/core.py +++ b/src/ipumspy/api/core.py @@ -24,6 +24,7 @@ IpumsNotFound, IpumsTimeoutException, TransientIpumsApiException, + IpumsApiRateLimitException, ) from .extract import ( BaseExtract, @@ -142,6 +143,8 @@ def request(self, method: str, *args, **kwargs) -> requests.Response: raise IpumsNotFound( "Page not found. Perhaps you passed the wrong extract id or an invalid page size?" ) + elif response.status_code == HTTPStatus.TOO_MANY_REQUESTS: + raise IpumsApiRateLimitException("You have exceeded the API rate limit.") else: error_details = _prettify_message(response.json()["detail"]) raise IpumsApiException(error_details) diff --git a/src/ipumspy/api/exceptions.py b/src/ipumspy/api/exceptions.py index 9076bd5..0f8565c 100644 --- a/src/ipumspy/api/exceptions.py +++ b/src/ipumspy/api/exceptions.py @@ -39,3 +39,6 @@ class BadIpumsApiRequest(IpumsApiException): class IpumsExtractNotSubmitted(IpumsApiException): """Represents the case when an extract needs to be submitted before the operation can be performed""" + +class IpumsApiRateLimitException(IpumsApiException): + """Represents a request that exceeds the IPUMS API rate limit""" \ No newline at end of file From 92a77c2c3874372bd561a640391b7a8cc9f501ad Mon Sep 17 00:00:00 2001 From: renae-r Date: Fri, 10 Jan 2025 13:51:26 -0600 Subject: [PATCH 29/35] doc tweaks and new logo --- docs/source/_static/ipumspy-logo.png | Bin 0 -> 66277 bytes docs/source/conf.py | 2 +- docs/source/ipums_api/index.rst | 22 ++++++++++-------- .../ipums_api/ipums_api_aggregate/index.rst | 8 ++++--- docs/source/reference/api.rst | 5 ++-- 5 files changed, 21 insertions(+), 16 deletions(-) create mode 100644 docs/source/_static/ipumspy-logo.png diff --git a/docs/source/_static/ipumspy-logo.png b/docs/source/_static/ipumspy-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..72b4b094af46e09db38f4294c1835ba3d9526c94 GIT binary patch literal 66277 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 literal 0 HcmV?d00001 diff --git a/docs/source/conf.py b/docs/source/conf.py index df2783b..a8cdd0d 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -123,7 +123,7 @@ # further. For a list of options available for each theme, see the # documentation. -html_logo = "_static/ipums-h-n-rgb.png" +html_logo = "_static/ipumspy-logo.png" html_favicon = "_static/ipums-favicon-152x152.png" html_theme_options = { "light_css_variables": { diff --git a/docs/source/ipums_api/index.rst b/docs/source/ipums_api/index.rst index 9de93ee..55c3b4c 100644 --- a/docs/source/ipums_api/index.rst +++ b/docs/source/ipums_api/index.rst @@ -144,7 +144,18 @@ AGE, SEX, RACE, STATEFIP, and MARST variables from the 2018 and 2019 American Co .. _ipums-metadata: IPUMS Metadata -************** +-------------- + +Microdata Collections +********************* + +.. note:: + Currently, comprehensive IPUMS API metadata is only available for IPUMS NHGIS. + For microdata collections, only sample information is available. You can also obtain a dictionary + of sample codes with :py:meth:`.get_all_sample_info`. + +Aggregate Data Collections +************************** You can use the IPUMS API metadata endpoints to identify the codes you can use to include particular data sources in your extract request. @@ -209,15 +220,6 @@ The following table summarizes the currently available metadata endpoints: * - ``shapefiles`` - IPUMS NHGIS - - * - ``samples`` - - Microdata collections - - - -.. note:: - Currently, comprehensive IPUMS API metadata is only available for IPUMS NHGIS. - For microdata collections, only sample information is available. You can also obtain a dictionary - of sample codes with :py:meth:`.get_all_sample_info`. - .. _submit-extract: diff --git a/docs/source/ipums_api/ipums_api_aggregate/index.rst b/docs/source/ipums_api/ipums_api_aggregate/index.rst index 688d940..96d7743 100644 --- a/docs/source/ipums_api/ipums_api_aggregate/index.rst +++ b/docs/source/ipums_api/ipums_api_aggregate/index.rst @@ -125,7 +125,7 @@ single file using the ``breakdown_and_data_type_layout`` argument. Dataset + Data Table Metadata +++++++++++++++++++++++++++++ -Use the :class:`DatasetMetadata ` class to browse the available +Use the :class:`DatasetMetadata ` data class to browse the available specification options for a particular dataset and identify the codes to use when requesting data from the API: @@ -149,7 +149,7 @@ The returned object will contain the metadata for the requested dataset. For exa # etc... -You can also request metadata for individual data tables using the same workflow with the :class:`DataTableMetadata ` class. +You can also request metadata for individual data tables using the same workflow with the :class:`DataTableMetadata ` data class. Geographic Extent Selection +++++++++++++++++++++++++++ @@ -317,11 +317,13 @@ Many NHGIS supplemental data files can be found under the "Supplemental Data" he for all supported supplemental data endpoints and advice on how to convert file URLs found on the website into acceptable API request URLs. -Once you've identified a file's location, you can use the :py:meth:`.get` method to download it. For +Once you've identified a file's location, you can use the``ipumspy`` :py:meth:`.get` method to download it. For instance, to download a state-level NHGIS crosswalk file, we could use the following: .. code:: python + ipums = IpumsApiClient(os.environ.get("IPUMS_API_KEY")) + file_name = "nhgis_blk2010_blk2020_10.zip" url = f"{ipums.base_url}/supplemental-data/nhgis/crosswalks/nhgis_blk2010_blk2020_state/{file_name}" diff --git a/docs/source/reference/api.rst b/docs/source/reference/api.rst index 7327ad7..53b8e1e 100644 --- a/docs/source/reference/api.rst +++ b/docs/source/reference/api.rst @@ -67,7 +67,7 @@ objects and from dictonary objects to ipumspy extract objects. IPUMS Metadata -------------- -Use these classes to request IPUMS metadata via the IPUMS API. +Use these classes and methods to request IPUMS metadata for aggregate data collections via the IPUMS API. .. autosummary:: :toctree: generated @@ -94,4 +94,5 @@ Several different exceptions may be raised when interacting with the IPUMS API. ipumspy.api.exceptions.IpumsTimeoutException ipumspy.api.exceptions.IpumsAPIAuthenticationError ipumspy.api.exceptions.BadIpumsApiRequest - ipumspy.api.exceptions.IpumsExtractNotSubmitted \ No newline at end of file + ipumspy.api.exceptions.IpumsExtractNotSubmitted + ipumspy.api.exceptions.IpumsApiRateLimitException \ No newline at end of file From e80340df5f2e421e32cbd737b68d778d1cc20cab Mon Sep 17 00:00:00 2001 From: renae-r Date: Fri, 10 Jan 2025 14:58:58 -0600 Subject: [PATCH 30/35] add test for rate limit exception --- .../test_ipums_api_rate_limit_exception.yaml | 5992 +++++++++++++++++ tests/test_metadata.py | 11 + 2 files changed, 6003 insertions(+) create mode 100644 tests/cassettes/test_metadata/test_ipums_api_rate_limit_exception.yaml diff --git a/tests/cassettes/test_metadata/test_ipums_api_rate_limit_exception.yaml b/tests/cassettes/test_metadata/test_ipums_api_rate_limit_exception.yaml new file mode 100644 index 0000000..296ad29 --- /dev/null +++ b/tests/cassettes/test_metadata/test_ipums_api_rate_limit_exception.yaml @@ -0,0 +1,5992 @@ +interactions: +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&version=2&pageSize=5 + response: + body: + string: '{"data":[{"name":"NT1","description":"Total Population","universe":"Persons","nhgisCode":"AAA","sequence":1,"datasetName":"1790_cPop","nVariables":1},{"name":"NT2","description":"Urban + Population (Incorporated Places 2,500 and Over)","universe":"Urban Persons + (Incorporated Places 2,500 and Over)","nhgisCode":"AAK","sequence":2,"datasetName":"1790_cPop","nVariables":1},{"name":"NT3","description":"Urban + Population in Cities of 25,000 and Over","universe":"Urban Persons in Cities + of 25,000 and Over","nhgisCode":"AAN","sequence":3,"datasetName":"1790_cPop","nVariables":1},{"name":"NT4","description":"White + Males and Estimated White Females by Sex by Age","universe":"White Persons","nhgisCode":"AAO","sequence":4,"datasetName":"1790_cPop","nVariables":4},{"name":"NT5","description":"White + Population by Sex","universe":"White Persons","nhgisCode":"AAP","sequence":5,"datasetName":"1790_cPop","nVariables":2}],"pageNumber":1,"pageSize":5,"totalCount":51509,"links":{"previousPage":null,"nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=2\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1114' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:14 GMT + Etag: + - W/"dd3794269c9809daa900272cdf65cd5b" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - 6551725b-188c-4b49-816b-98fad56d2a6a + X-Runtime: + - '0.021248' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=2&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT6","description":"Race/Slave Status","universe":"Persons","nhgisCode":"AAQ","sequence":6,"datasetName":"1790_cPop","nVariables":3},{"name":"NT9","description":"Total + Number of Families","universe":"Families","nhgisCode":"AAR","sequence":7,"datasetName":"1790_cPop","nVariables":1},{"name":"NT10","description":"Number + of Family Members","universe":"Families","nhgisCode":"AAB","sequence":8,"datasetName":"1790_cPop","nVariables":11},{"name":"NT12","description":"White + Population by Nationality","universe":"White Persons","nhgisCode":"AAC","sequence":9,"datasetName":"1790_cPop","nVariables":8},{"name":"NT13","description":"Families + by Slaveholding Status","universe":"Families","nhgisCode":"AAD","sequence":10,"datasetName":"1790_cPop","nVariables":2}],"pageNumber":2,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=1\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=3\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1081' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:14 GMT + Etag: + - W/"41f2cd59dbdd7f289459997715ba3652" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - 33d1c863-909b-4712-bd76-a3789069cb3d + X-Runtime: + - '0.007117' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=3&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT14","description":"Families by Slaveholding Status + by Race","universe":"Families","nhgisCode":"AAE","sequence":11,"datasetName":"1790_cPop","nVariables":4},{"name":"NT15","description":"White + Family Members by Slaveholding Status","universe":"White Family Members","nhgisCode":"AAF","sequence":12,"datasetName":"1790_cPop","nVariables":2},{"name":"NT16","description":"Average + Number of Members in White Families by Slaveholding Status","universe":"White + Families","nhgisCode":"AAG","sequence":13,"datasetName":"1790_cPop","nVariables":2},{"name":"NT17","description":"Percent + of Families by Slaveholding Status by Race","universe":"Families","nhgisCode":"AAH","sequence":14,"datasetName":"1790_cPop","nVariables":4},{"name":"NT18","description":"Total + Number of Slaves","universe":"Slaves","nhgisCode":"AAI","sequence":15,"datasetName":"1790_cPop","nVariables":1}],"pageNumber":3,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=2\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=4\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1191' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:14 GMT + Etag: + - W/"82814be73d4493ab475fe45814ec56bd" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - 80232b08-1ce5-44c6-b125-c4b018556bfa + X-Runtime: + - '0.010405' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=4&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT19","description":"Average Number of Slaves per + Slaveholding Family","universe":"Slaves","nhgisCode":"AAJ","sequence":16,"datasetName":"1790_cPop","nVariables":1},{"name":"NT20","description":"Slaveholding + Families by Number of Slaves","universe":"Slaveholding Families","nhgisCode":"AAL","sequence":17,"datasetName":"1790_cPop","nVariables":10},{"name":"NT21","description":"Race","universe":"Persons","nhgisCode":"AAM","sequence":18,"datasetName":"1790_cPop","nVariables":2},{"name":"NT1","description":"Total + Population (from Original Census Report)","universe":"Persons","nhgisCode":"AAS","sequence":1,"datasetName":"1800_cPop","nVariables":1},{"name":"NT2","description":"Total + Population (from 1900 Census Report)","universe":"Persons","nhgisCode":"AAU","sequence":2,"datasetName":"1800_cPop","nVariables":1}],"pageNumber":4,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=3\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=5\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1141' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:14 GMT + Etag: + - W/"b2b110915c1e96598d2e0770d7b067c7" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - 65fa5003-bca0-4029-a967-d8a1dc1e1e1a + X-Runtime: + - '0.009843' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=5&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT3","description":"Total Urban Population","universe":"Urban + Persons","nhgisCode":"AAV","sequence":3,"datasetName":"1800_cPop","nVariables":1},{"name":"NT4","description":"Total + Urban Population in Cities of 25,000 and Over","universe":"Urban Persons in + Cities of 25,000 and Over","nhgisCode":"AAW","sequence":4,"datasetName":"1800_cPop","nVariables":1},{"name":"NT5","description":"Free + White Population by Sex by Age","universe":"Free White Persons","nhgisCode":"AAX","sequence":5,"datasetName":"1800_cPop","nVariables":10},{"name":"NT6","description":"Nonwhite + Population, Except Indians Not Taxed by Slave Status","universe":"Non-White + Persons Except Indians Not Taxed","nhgisCode":"AAY","sequence":6,"datasetName":"1800_cPop","nVariables":2},{"name":"NT8","description":"Total + Population","universe":"Persons","nhgisCode":"AAZ","sequence":7,"datasetName":"1800_cPop","nVariables":1}],"pageNumber":5,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=4\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=6\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1213' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:14 GMT + Etag: + - W/"4c2842c6714869b53b4c3df7ad1daaed" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - 7c0f5094-b861-4ec2-8636-919cefd8e0f4 + X-Runtime: + - '0.007313' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=6&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT9","description":"White Population by Sex","universe":"White + Persons","nhgisCode":"AA0","sequence":8,"datasetName":"1800_cPop","nVariables":2},{"name":"NT10","description":"Race","universe":"Persons","nhgisCode":"AAT","sequence":9,"datasetName":"1800_cPop","nVariables":2},{"name":"NT1","description":"Total + Population (from Original Census Report)","universe":"Persons","nhgisCode":"AA1","sequence":1,"datasetName":"1810_cPop","nVariables":1},{"name":"NT2","description":"Total + Population (from 1900 Census Report)","universe":"Persons","nhgisCode":"AA3","sequence":2,"datasetName":"1810_cPop","nVariables":1},{"name":"NT3","description":"Urban + Population 2,500 and Over","universe":"Urban Persons (Incorporated Places + 2,500 and Over)","nhgisCode":"AA4","sequence":3,"datasetName":"1810_cPop","nVariables":1}],"pageNumber":6,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=5\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=7\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1136' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:14 GMT + Etag: + - W/"0f45ab938eb51c3ab5855f152927aeb8" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - 0138a4f1-c199-4562-8ff7-10df4d0e2894 + X-Runtime: + - '0.008119' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=7&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT4","description":"Urban Population in Cities of + 25,000 and Over","universe":"Urban Persons in Cities of 25,000 and Over","nhgisCode":"AA5","sequence":4,"datasetName":"1810_cPop","nVariables":1},{"name":"NT5","description":"Free + White Population by Sex by Age","universe":"Free White Persons","nhgisCode":"AA6","sequence":5,"datasetName":"1810_cPop","nVariables":10},{"name":"NT6","description":"Nonwhite + Population, Except Indians Not Taxed by Slave Status","universe":"Non-White + Persons Except Indians Not Taxed","nhgisCode":"AA7","sequence":6,"datasetName":"1810_cPop","nVariables":2},{"name":"NT8","description":"Total + Population","universe":"Persons","nhgisCode":"AA8","sequence":7,"datasetName":"1810_cPop","nVariables":1},{"name":"NT9","description":"White + Population by Sex","universe":"White Persons","nhgisCode":"AA9","sequence":8,"datasetName":"1810_cPop","nVariables":2}],"pageNumber":7,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=6\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=8\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1208' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:14 GMT + Etag: + - W/"5bcb1307054546b98b6f9fdebbfa3cb4" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - 77a35bf2-9273-468b-9b17-9e646b27d81b + X-Runtime: + - '0.010374' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=8&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT10","description":"Race","universe":"Persons","nhgisCode":"AA2","sequence":9,"datasetName":"1810_cPop","nVariables":2},{"name":"NT1","description":"Total + Population","universe":"Persons","nhgisCode":"ABA","sequence":1,"datasetName":"1820_cPop","nVariables":1},{"name":"NT2","description":"Urban + Population 2,500 and Over","universe":"Urban Persons (Incorporated Places + 2,500 and Over)","nhgisCode":"ABE","sequence":2,"datasetName":"1820_cPop","nVariables":1},{"name":"NT3","description":"Urban + Population in Cities of 25,000 and Over","universe":"Urban Persons in Cities + of 25,000 and Over","nhgisCode":"ABF","sequence":3,"datasetName":"1820_cPop","nVariables":1},{"name":"NT4A","description":"Free + White Population by Sex by Age","universe":"Free White Persons with Sex and + Age Specified","nhgisCode":"ABG","sequence":4,"datasetName":"1820_cPop","nVariables":10}],"pageNumber":8,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=7\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=9\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1190' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:14 GMT + Etag: + - W/"37c46c92e0ef471ff4708e9e7856018a" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - 41d13a5e-ba3b-4872-a77e-d2d5821608e2 + X-Runtime: + - '0.010146' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=9&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT4B","description":"Free White Males 16 to 18 Years + of Age","universe":"Free White Males 16 to 18 Years of Age","nhgisCode":"ABH","sequence":5,"datasetName":"1820_cPop","nVariables":1},{"name":"NT5","description":"Total + Foreign Persons Not Naturalized","universe":"Foreign-Born Persons Not Naturalized","nhgisCode":"ABI","sequence":6,"datasetName":"1820_cPop","nVariables":1},{"name":"NT6","description":"Number + of Persons Engaged in Selected Industry","universe":"Persons Engaged in Agriculture, + Commerce and Manufacturing","nhgisCode":"ABJ","sequence":7,"datasetName":"1820_cPop","nVariables":3},{"name":"NT7","description":"Colored + Population by Slave Status by Sex by Age","universe":"Colored Persons with + Sex and Age Specified","nhgisCode":"ABK","sequence":8,"datasetName":"1820_cPop","nVariables":16},{"name":"NT8","description":"Free + Population with Race, Sex, and Age Unspecified Except Indians Not Taxed","universe":"Free + Persons with Race, Sex, and Age Unspecified Except Indians Not Taxed","nhgisCode":"ABL","sequence":9,"datasetName":"1820_cPop","nVariables":1}],"pageNumber":9,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=8\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=10\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1400' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:14 GMT + Etag: + - W/"2e652e100a00d2859db6739ba5cc4d2f" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - a3c6fbb8-a80d-4c05-abae-7525825fe12a + X-Runtime: + - '0.009697' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=10&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT9","description":"Total Population","universe":"Persons","nhgisCode":"ABM","sequence":10,"datasetName":"1820_cPop","nVariables":1},{"name":"NT10","description":"Race/Slave + Status by Sex","universe":"Persons with Sex and Age Specified","nhgisCode":"ABB","sequence":11,"datasetName":"1820_cPop","nVariables":6},{"name":"NT12","description":"Sex","universe":"Persons + with Sex and Age Specified","nhgisCode":"ABC","sequence":12,"datasetName":"1820_cPop","nVariables":2},{"name":"NT13","description":"Race","universe":"Persons + with Sex and Age Specified","nhgisCode":"ABD","sequence":13,"datasetName":"1820_cPop","nVariables":2},{"name":"NT1","description":"Total + Population","universe":"Persons","nhgisCode":"ABN","sequence":1,"datasetName":"1830_cPop","nVariables":1}],"pageNumber":10,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=9\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=11\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1093' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:14 GMT + Etag: + - W/"39d1619d5772c055b92ec0a4c5b36f2c" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - 548ed06b-4a52-4c8f-b64f-8307e1878371 + X-Runtime: + - '0.009348' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=11&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT2","description":"Urban Population 2,500 and Over","universe":"Urban + Persons (Incorporated Places 2,500 and Over)","nhgisCode":"ABU","sequence":2,"datasetName":"1830_cPop","nVariables":1},{"name":"NT3","description":"Urban + Population in Cities of 25,000 and Over","universe":"Urban Persons in Cities + of 25,000 and Over","nhgisCode":"ABV","sequence":3,"datasetName":"1830_cPop","nVariables":1},{"name":"NT4","description":"Free + White Population by Sex by Age","universe":"Free White Persons","nhgisCode":"ABW","sequence":4,"datasetName":"1830_cPop","nVariables":26},{"name":"NT5","description":"Colored + Population by Slave Status by Sex by Age","universe":"Colored Persons","nhgisCode":"ABX","sequence":5,"datasetName":"1830_cPop","nVariables":24},{"name":"NT6","description":"Total + Population from the Original Census","universe":"Persons","nhgisCode":"ABY","sequence":6,"datasetName":"1830_cPop","nVariables":1}],"pageNumber":11,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=10\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=12\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1242' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:15 GMT + Etag: + - W/"c4261bb6bce1781c2ceb06474a7313bf" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - 6d88f706-5309-4651-9315-f514ebe3c2e6 + X-Runtime: + - '0.006979' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=12&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT7","description":"Persons Who Are Deaf and Dumb + by Race by Age","universe":"Deaf and Dumb Persons","nhgisCode":"ABZ","sequence":7,"datasetName":"1830_cPop","nVariables":6},{"name":"NT8","description":"Persons + Who Are Blind by Race","universe":"Blind Persons","nhgisCode":"AB0","sequence":8,"datasetName":"1830_cPop","nVariables":2},{"name":"NT9","description":"Number + of Foreigners Not Naturalized","universe":"Foreign-Born Persons Not Naturalized","nhgisCode":"AB1","sequence":9,"datasetName":"1830_cPop","nVariables":1},{"name":"NT12","description":"Race/Slave + Status by Sex","universe":"Persons","nhgisCode":"ABO","sequence":10,"datasetName":"1830_cPop","nVariables":6},{"name":"NT13","description":"Race","universe":"Persons","nhgisCode":"ABP","sequence":11,"datasetName":"1830_cPop","nVariables":2}],"pageNumber":12,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=11\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=13\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1133' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:15 GMT + Etag: + - W/"0a0f3c9ecc06caa9fa10f478535ca4c8" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - 7727d24e-0617-48a6-ae95-923c223618b8 + X-Runtime: + - '0.009176' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=13&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT15","description":"Colored Population by Slave + Status","universe":"Colored Persons","nhgisCode":"ABQ","sequence":12,"datasetName":"1830_cPop","nVariables":2},{"name":"NT16","description":"Sex","universe":"Persons","nhgisCode":"ABR","sequence":13,"datasetName":"1830_cPop","nVariables":2},{"name":"NT17","description":"Total + Slave and Free Colored Population by Sex","universe":"Slave and Free Colored + Persons","nhgisCode":"ABS","sequence":14,"datasetName":"1830_cPop","nVariables":2},{"name":"NT19","description":"White + Population 80 Years of Age and Over by Sex","universe":"White Persons 80 Years + and Over","nhgisCode":"ABT","sequence":15,"datasetName":"1830_cPop","nVariables":2},{"name":"NT1A","description":"Livestock","universe":"Farms","nhgisCode":"AB2","sequence":1,"datasetName":"1840_cAg","nVariables":4}],"pageNumber":13,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=12\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=14\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1144' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:15 GMT + Etag: + - W/"3685de79fd102dcea2dbdf9efb7793f0" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - 4e22171e-778f-4a48-a73e-93292669761e + X-Runtime: + - '0.008751' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=14&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT1B","description":"Value of Poultry","universe":"Farms","nhgisCode":"AB3","sequence":2,"datasetName":"1840_cAg","nVariables":1},{"name":"NT2","description":"Farm + Productions","universe":"Farms","nhgisCode":"AB4","sequence":3,"datasetName":"1840_cAg","nVariables":22},{"name":"NT3","description":"Value + of Non-Field-Crop Products","universe":"Farms","nhgisCode":"AB5","sequence":4,"datasetName":"1840_cAg","nVariables":5},{"name":"NT4","description":"Production + Groups and Specialty Products","universe":"Farms","nhgisCode":"AB6","sequence":5,"datasetName":"1840_cAg","nVariables":4},{"name":"NT5","description":"Price + of Farm Productions","universe":"Farms","nhgisCode":"AB7","sequence":6,"datasetName":"1840_cAg","nVariables":22}],"pageNumber":14,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=13\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=15\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1060' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:15 GMT + Etag: + - W/"c549945f6719e466a73c685aa8e225be" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - 4a5c1d6b-d5f7-4eeb-a7aa-a71836e8e7b4 + X-Runtime: + - '0.011632' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=15&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT6","description":"Estimated Value of Farm Productions","universe":"Farms","nhgisCode":"AB8","sequence":7,"datasetName":"1840_cAg","nVariables":22},{"name":"NT7A","description":"Estimated + Total Crop Output Value","universe":"Farms","nhgisCode":"AB9","sequence":8,"datasetName":"1840_cAg","nVariables":1},{"name":"NT7B","description":"Value + of Production Groups","universe":"Farms","nhgisCode":"ACA","sequence":9,"datasetName":"1840_cAg","nVariables":3},{"name":"NT8","description":"Estimated + Value of Agricultural Output","universe":"Farms","nhgisCode":"ACB","sequence":10,"datasetName":"1840_cAg","nVariables":1},{"name":"NT9","description":"Population","universe":"Persons","nhgisCode":"ACC","sequence":11,"datasetName":"1840_cAg","nVariables":1}],"pageNumber":15,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=14\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=16\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1077' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:15 GMT + Etag: + - W/"0bdb4c1a25d7c7c85b31b538e07fa570" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - 2998ddb8-7ba4-4227-9a59-1ecebbc5c15f + X-Runtime: + - '0.009175' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=16&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT1","description":"Total Population","universe":"Persons","nhgisCode":"AC4","sequence":1,"datasetName":"1840_cMfg","nVariables":1},{"name":"NT2","description":"Fuel + Consumed in Iron Manufacturing","universe":"Iron Manufacturing Establishments","nhgisCode":"AC7","sequence":2,"datasetName":"1840_cMfg","nVariables":1},{"name":"NT3","description":"Total + Capital Invested in Manufactures","universe":"Manufacturing Establishments","nhgisCode":"AC8","sequence":3,"datasetName":"1840_cMfg","nVariables":1},{"name":"NT4","description":"Type + of Establishment","universe":"Establishments of Selected Industries","nhgisCode":"AC9","sequence":4,"datasetName":"1840_cMfg","nVariables":30},{"name":"NT5","description":"Value + of Production","universe":"Establishments of Selected Industries","nhgisCode":"ADA","sequence":5,"datasetName":"1840_cMfg","nVariables":41}],"pageNumber":16,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=15\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=17\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1181' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:15 GMT + Etag: + - W/"b1f82f51af0daf75825ec9e3e41d0198" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - 9c462e3b-1aeb-45bf-b7d4-53efe55ec232 + X-Runtime: + - '0.009707' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=17&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT6","description":"Production of Goods","universe":"Establishments + of Selected Industries","nhgisCode":"ADB","sequence":6,"datasetName":"1840_cMfg","nVariables":30},{"name":"NT7","description":"Capital + Invested","universe":"Establishments of Selected Industries","nhgisCode":"ADC","sequence":7,"datasetName":"1840_cMfg","nVariables":39},{"name":"NT8","description":"Employment","universe":"Employed + Persons in Selected Industries","nhgisCode":"ADD","sequence":8,"datasetName":"1840_cMfg","nVariables":7},{"name":"NT9","description":"Men + Employed by Type of Establishment for Selected Industries Employing Only Men","universe":"Males + Employed in Establishments of Selected Industries Employing Only Men","nhgisCode":"ADE","sequence":9,"datasetName":"1840_cMfg","nVariables":36},{"name":"NT10","description":"Persons + Employed by Type of Establishment for Textile and Leather Manufacturing Establishments","universe":"Employed + Persons in Textile and Leather Manufacturing Establishments","nhgisCode":"AC5","sequence":10,"datasetName":"1840_cMfg","nVariables":9}],"pageNumber":17,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=16\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=18\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1387' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:15 GMT + Etag: + - W/"ccff20af72081bd08ff1bfd00355bbc2" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - 8bffd3d4-d107-47ee-bf88-67379f566dd0 + X-Runtime: + - '0.008160' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=18&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT11","description":"Number of Spindles","universe":"Cotton + Manufacturing Establishments","nhgisCode":"AC6","sequence":11,"datasetName":"1840_cMfg","nVariables":1},{"name":"NT1","description":"Total + Population","universe":"Persons","nhgisCode":"ACD","sequence":1,"datasetName":"1840_cPopX","nVariables":1},{"name":"NT2","description":"Total + Urban Population","universe":"Urban Persons","nhgisCode":"ACN","sequence":2,"datasetName":"1840_cPopX","nVariables":1},{"name":"NT3","description":"Total + Urban Population in Cities of 25,000 and Over","universe":"Urban Persons in + Cities of 25,000 and Over","nhgisCode":"ACW","sequence":3,"datasetName":"1840_cPopX","nVariables":1},{"name":"NT4","description":"Free + White Population by Sex by Age","universe":"Free White Persons","nhgisCode":"ACY","sequence":4,"datasetName":"1840_cPopX","nVariables":26}],"pageNumber":18,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=17\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=19\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1172' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:15 GMT + Etag: + - W/"95e654d00223c43b9aee877e14faf04e" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - f71154ad-1d3f-4a6f-94e9-037705090f32 + X-Runtime: + - '0.006973' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=19&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT5","description":"Colored Population by Slave Status + by Sex by Age","universe":"Colored Persons","nhgisCode":"ACZ","sequence":5,"datasetName":"1840_cPopX","nVariables":24},{"name":"NT6","description":"Total + Population","universe":"Persons","nhgisCode":"AC0","sequence":6,"datasetName":"1840_cPopX","nVariables":1},{"name":"NT7","description":"Employed + Population by Occupation","universe":"Employed Persons","nhgisCode":"AC1","sequence":7,"datasetName":"1840_cPopX","nVariables":7},{"name":"NT8","description":"Total + Pensioners for Revolutionary War or other Military Services","universe":"Pensioners + for Revolutionary War or Other Military Services","nhgisCode":"AC2","sequence":8,"datasetName":"1840_cPopX","nVariables":1},{"name":"NT9","description":"Number + of Deaf and Dumb White Persons by Age","universe":"Deaf and Dumb White Persons","nhgisCode":"AC3","sequence":9,"datasetName":"1840_cPopX","nVariables":3}],"pageNumber":19,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=18\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=20\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1244' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:15 GMT + Etag: + - W/"57fec3e8602b62180c05f56b4ebb1818" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - 0e710a25-02e7-4a2f-9732-bc8821a85569 + X-Runtime: + - '0.010553' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=20&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT10","description":"Blind Persons by Race","universe":"Blind + Persons","nhgisCode":"ACE","sequence":10,"datasetName":"1840_cPopX","nVariables":2},{"name":"NT11","description":"Insane + and Idiot Persons by Race by Public/Private Charge","universe":"Insane and + Idiot Persons","nhgisCode":"ACF","sequence":11,"datasetName":"1840_cPopX","nVariables":4},{"name":"NT12","description":"Deaf + and Dumb Colored Persons of All Ages","universe":"Deaf and Dumb Colored Persons","nhgisCode":"ACG","sequence":12,"datasetName":"1840_cPopX","nVariables":1},{"name":"NT13","description":"Number + of Learning Institutions","universe":"Learning Institutions","nhgisCode":"ACH","sequence":13,"datasetName":"1840_cPopX","nVariables":3},{"name":"NT14","description":"Number + of Students by Type of Learning Institutions","universe":"Students","nhgisCode":"ACI","sequence":14,"datasetName":"1840_cPopX","nVariables":3}],"pageNumber":20,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=19\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=21\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1219' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:15 GMT + Etag: + - W/"b985b9847d07969f79e3367ea68a686c" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - a3b167e1-ca6e-40c8-86c3-69a211bf1d94 + X-Runtime: + - '0.007353' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=21&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT15","description":"Total Scholars at Public Charge","universe":"Scholars + at Public Charge","nhgisCode":"ACJ","sequence":15,"datasetName":"1840_cPopX","nVariables":1},{"name":"NT16","description":"Total + White Population Over Twenty Years of Age Who Cannot Read and Write","universe":"White + Persons Over 20 Years Who Cannot Read and Write","nhgisCode":"ACK","sequence":16,"datasetName":"1840_cPopX","nVariables":1},{"name":"NT17","description":"Number + of Printing and Binding Services","universe":"Printing Offices and Binderies","nhgisCode":"ACL","sequence":17,"datasetName":"1840_cPopX","nVariables":2},{"name":"NT19","description":"Newspapers + by Frequency of Issue","universe":"Periodicals","nhgisCode":"ACM","sequence":18,"datasetName":"1840_cPopX","nVariables":4},{"name":"NT21","description":"Total + Persons Employed in Printing and Binding","universe":"Employed Persons in + Printing and Binding Services","nhgisCode":"ACO","sequence":19,"datasetName":"1840_cPopX","nVariables":1}],"pageNumber":21,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=20\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=22\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1312' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:15 GMT + Etag: + - W/"4c5f08e722e734594ae8b2bea2dd03f7" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - 1b55b8f4-b6a7-4e0c-8167-78cf25104e59 + X-Runtime: + - '0.010009' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=22&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT22","description":"Total Capital Invested in Printing + and Binding","universe":"Printing Offices and Binderies","nhgisCode":"ACP","sequence":20,"datasetName":"1840_cPopX","nVariables":1},{"name":"NT23","description":"Total + Capital Invested in Manufacturing","universe":"Manufacturing Establishments","nhgisCode":"ACQ","sequence":21,"datasetName":"1840_cPopX","nVariables":1},{"name":"NT24","description":"White + Population by Sex","universe":"White Persons","nhgisCode":"ACR","sequence":22,"datasetName":"1840_cPopX","nVariables":2},{"name":"NT25","description":"Race/Slave + Status","universe":"Persons","nhgisCode":"ACS","sequence":23,"datasetName":"1840_cPopX","nVariables":3},{"name":"NT26","description":"White + Population 80 Years of Age and Over by Sex","universe":"White Persons 80 Years + and Over","nhgisCode":"ACT","sequence":24,"datasetName":"1840_cPopX","nVariables":2}],"pageNumber":22,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=21\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=23\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1205' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:16 GMT + Etag: + - W/"a39e2e9a57f9d8c05884ab3b0021c17f" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - a606dbd9-8906-4469-9af5-71650bebd102 + X-Runtime: + - '0.010047' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=23&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT28","description":"Colored Population by Slave + Status by Sex","universe":"Colored Persons","nhgisCode":"ACU","sequence":25,"datasetName":"1840_cPopX","nVariables":4},{"name":"NT29","description":"Sex","universe":"Persons","nhgisCode":"ACV","sequence":26,"datasetName":"1840_cPopX","nVariables":2},{"name":"NT30","description":"Navigable + Waterway","universe":"Specified Geographic Area","nhgisCode":"ACX","sequence":27,"datasetName":"1840_cPopX","nVariables":1},{"name":"NT1","description":"Total + Number of Farms","universe":"Farms","nhgisCode":"ADF","sequence":1,"datasetName":"1850_cAg","nVariables":1},{"name":"NT2","description":"Improved/Unimproved + Land in Farms","universe":"Farms","nhgisCode":"ADI","sequence":2,"datasetName":"1850_cAg","nVariables":2}],"pageNumber":23,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=22\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=24\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1088' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:16 GMT + Etag: + - W/"5a921d0d99e192b145baf4913476a93e" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - 8ebd977a-55c5-49de-82ea-0193ebf5c325 + X-Runtime: + - '0.018249' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=24&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT3","description":"Value of Farms, Implements and + Machinery","universe":"Farms","nhgisCode":"ADJ","sequence":3,"datasetName":"1850_cAg","nVariables":2},{"name":"NT4","description":"Livestock","universe":"Farms","nhgisCode":"ADK","sequence":4,"datasetName":"1850_cAg","nVariables":7},{"name":"NT5","description":"Total + Value of Livestock","universe":"Farms","nhgisCode":"ADL","sequence":5,"datasetName":"1850_cAg","nVariables":1},{"name":"NT6","description":"Farm + Productions","universe":"Farms","nhgisCode":"ADM","sequence":6,"datasetName":"1850_cAg","nVariables":30},{"name":"NT7","description":"Value + of Non-Field-Crop Product Groups","universe":"Farms","nhgisCode":"ADN","sequence":7,"datasetName":"1850_cAg","nVariables":4}],"pageNumber":24,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=23\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=25\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1056' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:16 GMT + Etag: + - W/"69833ee47c0f681f0ed07fd1e166f4d7" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - 4665d260-10e9-43b0-ba30-e8e89e4d36d5 + X-Runtime: + - '0.012056' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=25&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT8","description":"Price of Farm Productions, 1859/1860","universe":"Farms","nhgisCode":"ADO","sequence":8,"datasetName":"1850_cAg","nVariables":30},{"name":"NT9","description":"Estimated + Value of Farm Productions","universe":"Farms","nhgisCode":"ADP","sequence":9,"datasetName":"1850_cAg","nVariables":30},{"name":"NT10","description":"Estimated + Value of Farm Product Groups","universe":"Farms","nhgisCode":"ADG","sequence":10,"datasetName":"1850_cAg","nVariables":2},{"name":"NT11","description":"Estimated + Value of Total Agricultural Output","universe":"Farms","nhgisCode":"ADH","sequence":11,"datasetName":"1850_cAg","nVariables":1},{"name":"NT1","description":"Total + Population","universe":"Persons","nhgisCode":"ADQ","sequence":1,"datasetName":"1850_cPAX","nVariables":1}],"pageNumber":25,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=24\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=26\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1106' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:16 GMT + Etag: + - W/"fd7fa09950128cc392896fb905445452" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - 45216b09-9bb6-4068-9737-f35f4280c89e + X-Runtime: + - '0.010300' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=26&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT2","description":"Urban Population 2,500 and Over","universe":"Urban + Persons (Incorporated Places 2,500 and Over)","nhgisCode":"ADZ","sequence":2,"datasetName":"1850_cPAX","nVariables":1},{"name":"NT3","description":"Population + in Cities of 25,000 and Over","universe":"Persons in Cities of 25,000 and + Over","nhgisCode":"AD9","sequence":3,"datasetName":"1850_cPAX","nVariables":1},{"name":"NT4","description":"Race/Slave + Status by Sex by Age","universe":"Persons","nhgisCode":"AEL","sequence":4,"datasetName":"1850_cPAX","nVariables":90},{"name":"NT5","description":"Race/Slave + Status by Sex","universe":"Persons","nhgisCode":"AEW","sequence":5,"datasetName":"1850_cPAX","nVariables":6},{"name":"NT6","description":"Race/Slave + Status","universe":"Persons","nhgisCode":"AE6","sequence":6,"datasetName":"1850_cPAX","nVariables":3}],"pageNumber":26,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=25\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=27\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1158' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:16 GMT + Etag: + - W/"9306e3ef99da92e192dc5f7ec5c373ab" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - 726b0935-7078-4bef-839a-6675e68074d9 + X-Runtime: + - '0.014050' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=27&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT10","description":"Births by Slave Status","universe":"Births + During the Year Ending June 1, 1850","nhgisCode":"ADR","sequence":7,"datasetName":"1850_cPAX","nVariables":2},{"name":"NT12","description":"Total + Births","universe":"Births During the Year Ending June 1, 1850","nhgisCode":"ADS","sequence":8,"datasetName":"1850_cPAX","nVariables":1},{"name":"NT13","description":"Total + White Persons Married During the Year Ending June 1, 1850.","universe":"White + Persons Married During the Year Ending June 1, 1850","nhgisCode":"ADT","sequence":9,"datasetName":"1850_cPAX","nVariables":1},{"name":"NT14","description":"Total + Persons Married During the Year Ending June 1, 1850.","universe":"Persons + Married During the Year Ending June 1, 1850","nhgisCode":"ADU","sequence":10,"datasetName":"1850_cPAX","nVariables":1},{"name":"NT15","description":"Deaths + During the Year Ending June 1, 1850 by Slave Status","universe":"Deaths During + the Year Ending June 1, 1850","nhgisCode":"ADV","sequence":11,"datasetName":"1850_cPAX","nVariables":2}],"pageNumber":27,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=26\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=28\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1363' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:16 GMT + Etag: + - W/"76439b889c0b2ce971dfcbe122b985bd" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - 7cbd1d07-5c5f-44be-a31b-334e7beecb4f + X-Runtime: + - '0.007703' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=28&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT17","description":"Total Deaths During the Year + Ending June 1, 1850","universe":"Deaths During the Year Ending June 1, 1850","nhgisCode":"ADW","sequence":12,"datasetName":"1850_cPAX","nVariables":1},{"name":"NT18","description":"Total + Dwellings of White and Free-Colored Persons","universe":"Dwellings of White + and Free-Colored Persons","nhgisCode":"ADX","sequence":13,"datasetName":"1850_cPAX","nVariables":1},{"name":"NT19","description":"Total + White and Free-Colored Families","universe":"White and Free Colored Families","nhgisCode":"ADY","sequence":14,"datasetName":"1850_cPAX","nVariables":1},{"name":"NT20","description":"Types + of Learning Institutions","universe":"Learning Institutions","nhgisCode":"AD0","sequence":15,"datasetName":"1850_cPAX","nVariables":3},{"name":"NT21","description":"Persons + at Learning Institutions by Type of Learning Institution","universe":"Teachers + and Pupils in Learning Institutions","nhgisCode":"AD1","sequence":16,"datasetName":"1850_cPAX","nVariables":6}],"pageNumber":28,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=27\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=29\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1327' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:16 GMT + Etag: + - W/"fa76ef1606ff69b996a43a4b9ba05f7c" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - 9b49e21a-344a-40a5-9af2-a29bf793e708 + X-Runtime: + - '0.009889' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=29&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT23","description":"Annual Income by Types of Learning + Institutions by Income Source","universe":"Learning Institutions","nhgisCode":"AD2","sequence":17,"datasetName":"1850_cPAX","nVariables":12},{"name":"NT24","description":"Total + Annual Income by Types of Learning Institutions","universe":"Learning Institutions","nhgisCode":"AD3","sequence":18,"datasetName":"1850_cPAX","nVariables":3},{"name":"NT25","description":"White + and Free Colored Persons Attending School by Race by Sex","universe":"White + and Free Colored Persons Attending School","nhgisCode":"AD4","sequence":19,"datasetName":"1850_cPAX","nVariables":4},{"name":"NT26","description":"Persons + Attending School by Race","universe":"Persons Attending School","nhgisCode":"AD5","sequence":20,"datasetName":"1850_cPAX","nVariables":2},{"name":"NT27","description":"Persons + Attending School by Nativity","universe":"Persons Attending School","nhgisCode":"AD6","sequence":21,"datasetName":"1850_cPAX","nVariables":2}],"pageNumber":29,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=28\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=30\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1303' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:16 GMT + Etag: + - W/"890b13aefa222ad99daf821ef6ca8be8" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - 213724a8-4351-4b24-ae4c-eb256b7a0471 + X-Runtime: + - '0.010678' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=30&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT28","description":"Aggregate Number of Persons + Attending School","universe":"Persons Attending School","nhgisCode":"AD7","sequence":22,"datasetName":"1850_cPAX","nVariables":1},{"name":"NT29","description":"White + and Free Colored Adults Who Cannot Read and Write by Race by Sex","universe":"White + and Free Colored Adults Who Cannot Read and Write","nhgisCode":"AD8","sequence":23,"datasetName":"1850_cPAX","nVariables":4},{"name":"NT30","description":"White + and Free Colored Adults Who Cannot Read and Write by Race","universe":"White + and Free Colored Adults Who Cannot Read and Write","nhgisCode":"AEA","sequence":24,"datasetName":"1850_cPAX","nVariables":2},{"name":"NT31","description":"Adult + Persons Who Cannot Read and Write by Nativity","universe":"Adult Persons Who + Cannot Read and Write","nhgisCode":"AEB","sequence":25,"datasetName":"1850_cPAX","nVariables":2},{"name":"NT32","description":"Aggregate + Number of Adults Who Cannot Read and Write","universe":"Adult Persons Who + Cannot Read and Write","nhgisCode":"AEC","sequence":26,"datasetName":"1850_cPAX","nVariables":1}],"pageNumber":30,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=29\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=31\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1410' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:16 GMT + Etag: + - W/"1cd5259b509db7a77e6bdcf33cfc2e8f" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - d04af877-3b93-4986-b904-cbea7bdf3a42 + X-Runtime: + - '0.009822' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=31&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT33","description":"Improved/Unimproved Land in + Farms","universe":"Farms","nhgisCode":"AED","sequence":27,"datasetName":"1850_cPAX","nVariables":2},{"name":"NT34","description":"Total + Cash Value of Farms","universe":"Farms","nhgisCode":"AEE","sequence":28,"datasetName":"1850_cPAX","nVariables":1},{"name":"NT35A","description":"Value + of Farm Assets","universe":"Farms","nhgisCode":"AEF","sequence":29,"datasetName":"1850_cPAX","nVariables":2},{"name":"NT35B","description":"Value + of Farm Productions","universe":"Farms","nhgisCode":"AEG","sequence":30,"datasetName":"1850_cPAX","nVariables":4},{"name":"NT36","description":"Types + of Libraries","universe":"Libraries","nhgisCode":"AEH","sequence":31,"datasetName":"1850_cPAX","nVariables":5}],"pageNumber":31,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=30\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=32\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1070' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:17 GMT + Etag: + - W/"d8ff57a4fd80cd49a9a5ebf8d9d66828" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - bfbda542-d261-4c14-a531-b555e3c4b089 + X-Runtime: + - '0.009174' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=32&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT37","description":"Volumes in Libraries by Types + of Libraries","universe":"Libraries","nhgisCode":"AEI","sequence":32,"datasetName":"1850_cPAX","nVariables":5},{"name":"NT38","description":"Total + Number of Libraries","universe":"Libraries","nhgisCode":"AEJ","sequence":33,"datasetName":"1850_cPAX","nVariables":1},{"name":"NT39","description":"Total + Number of Volumes in Libraries","universe":"Libraries","nhgisCode":"AEK","sequence":34,"datasetName":"1850_cPAX","nVariables":1},{"name":"NT40","description":"Nativity","universe":"Persons","nhgisCode":"AEM","sequence":35,"datasetName":"1850_cPAX","nVariables":2},{"name":"NT41","description":"Total + Educational Income","universe":"Learning Institutions","nhgisCode":"AEN","sequence":36,"datasetName":"1850_cPAX","nVariables":1}],"pageNumber":32,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=31\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=33\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1108' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:17 GMT + Etag: + - W/"294930d1e927a9282100602fe225f9f3" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - 792b1ea0-db9a-4bfd-8c5a-16f7962bccb9 + X-Runtime: + - '0.008235' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=33&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT42","description":"Total Persons 5 to 19 Years + of Age","universe":"Persons 5 to 19 Years of Age","nhgisCode":"AEO","sequence":37,"datasetName":"1850_cPAX","nVariables":1},{"name":"NT43","description":"Total + Number of Farms","universe":"Farms","nhgisCode":"AEP","sequence":38,"datasetName":"1850_cPAX","nVariables":1},{"name":"NT44","description":"Total + Capital Invested in Manufacturing Establishments","universe":"Manufacturing + Establishments","nhgisCode":"AEQ","sequence":39,"datasetName":"1850_cPAX","nVariables":1},{"name":"NT45","description":"Total + Persons Employed in Manufacturing Establishments","universe":"Employed Persons + in Manufacturing Establishments","nhgisCode":"AER","sequence":40,"datasetName":"1850_cPAX","nVariables":1},{"name":"NT46","description":"Total + Value of Annual Product in Manufacturing Establishments","universe":"Manufacturing + Establishments","nhgisCode":"AES","sequence":41,"datasetName":"1850_cPAX","nVariables":1}],"pageNumber":33,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=32\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=34\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1279' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:17 GMT + Etag: + - W/"1644367b7dfc5d0f949f1010e45a8ab7" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - 8a545446-8ef5-459e-81fc-43806744b657 + X-Runtime: + - '0.011138' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=34&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT47","description":"Churches by Denomination","universe":"Churches","nhgisCode":"AET","sequence":42,"datasetName":"1850_cPAX","nVariables":23},{"name":"NT48","description":"Aggregate + Accommodations of Churches by Denomination","universe":"Churches","nhgisCode":"AEU","sequence":43,"datasetName":"1850_cPAX","nVariables":23},{"name":"NT49","description":"Value + of Church Property by Denomination","universe":"Churches","nhgisCode":"AEV","sequence":44,"datasetName":"1850_cPAX","nVariables":23},{"name":"NT50","description":"Total + Number of Churches","universe":"Churches","nhgisCode":"AEX","sequence":45,"datasetName":"1850_cPAX","nVariables":1},{"name":"NT51","description":"Total + Aggregate Accommodations of Churches","universe":"Churches","nhgisCode":"AEY","sequence":46,"datasetName":"1850_cPAX","nVariables":1}],"pageNumber":34,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=33\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=35\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1143' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:17 GMT + Etag: + - W/"57d6287e9b8262bfef2f6c74ec3fc41e" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - 00dc41f8-4d75-40a2-9093-022259df0f25 + X-Runtime: + - '0.010237' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=35&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT52","description":"Total Value of Church Property","universe":"Churches","nhgisCode":"AEZ","sequence":47,"datasetName":"1850_cPAX","nVariables":1},{"name":"NT53","description":"Sex + by Age","universe":"Persons","nhgisCode":"AE0","sequence":48,"datasetName":"1850_cPAX","nVariables":30},{"name":"NT54","description":"Sex","universe":"Persons","nhgisCode":"AE1","sequence":49,"datasetName":"1850_cPAX","nVariables":2},{"name":"NT55","description":"Age","universe":"Persons","nhgisCode":"AE2","sequence":50,"datasetName":"1850_cPAX","nVariables":15},{"name":"NT56","description":"Total + White Population","universe":"White Persons","nhgisCode":"AE3","sequence":51,"datasetName":"1850_cPAX","nVariables":1}],"pageNumber":35,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=34\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=36\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1030' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:17 GMT + Etag: + - W/"7acff5b19d54fd302e06dcff2d09afe7" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - bf7b0d1c-f7c5-4993-97a5-bea8f7d94a0f + X-Runtime: + - '0.009390' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=36&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT57","description":"Water Transport","universe":"Specified + Geographic Area","nhgisCode":"AE4","sequence":52,"datasetName":"1850_cPAX","nVariables":1},{"name":"NT58","description":"Rail + Transport","universe":"Specified Geographic Area","nhgisCode":"AE5","sequence":53,"datasetName":"1850_cPAX","nVariables":1},{"name":"NT1","description":"Native-Born + White and Free Colored by Nativity by Race by Sex","universe":"Native White + and Free Colored Persons","nhgisCode":"AE8","sequence":1,"datasetName":"1850_sPAX","nVariables":8},{"name":"NT2","description":"White + and Free Colored Persons Born in the State by Race","universe":"White and + Free Colored Persons Born in the State","nhgisCode":"AFJ","sequence":2,"datasetName":"1850_sPAX","nVariables":2},{"name":"NT4","description":"White + Males Born out of State","universe":"White Males Born out of State","nhgisCode":"AF4","sequence":3,"datasetName":"1850_sPAX","nVariables":1}],"pageNumber":36,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=35\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=37\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1251' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:17 GMT + Etag: + - W/"58e8d3a45dd06c833d372ae256a61d54" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - e57cee9a-15c4-4b7b-a571-8e17148e1f51 + X-Runtime: + - '0.011878' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=37&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT5","description":"Foreign-Born Whites and Free + Colored by Race by Sex","universe":"Foreign-Born White and Free Colored Persons","nhgisCode":"AGF","sequence":4,"datasetName":"1850_sPAX","nVariables":4},{"name":"NT6","description":"Total + Foreign-Born White and Free Colored by Race","universe":"Foreign-Born White + and Free Colored Persons","nhgisCode":"AGM","sequence":5,"datasetName":"1850_sPAX","nVariables":2},{"name":"NT7","description":"White + and Free Colored Birthplace Unknown by Race by Sex","universe":"White and + Free Colored Persons Birthplace Unknown","nhgisCode":"AGN","sequence":6,"datasetName":"1850_sPAX","nVariables":4},{"name":"NT8","description":"White + and Free Colored Birthplace Unknown by Race","universe":"White and Free Colored + Persons Birthplace Unknown","nhgisCode":"AGO","sequence":7,"datasetName":"1850_sPAX","nVariables":2},{"name":"NT10","description":"Total + Free Colored Born out of State","universe":"Free Colored Persons Born Out + of State","nhgisCode":"AE9","sequence":8,"datasetName":"1850_sPAX","nVariables":1}],"pageNumber":37,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=36\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=38\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1372' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:17 GMT + Etag: + - W/"6337afad0a144571675514f240c53541" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - 6ad0b056-8a8d-4b2f-b697-f4068fec1388 + X-Runtime: + - '0.007540' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=38&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT11","description":"Nativity","universe":"Persons","nhgisCode":"AFA","sequence":9,"datasetName":"1850_sPAX","nVariables":2},{"name":"NT12","description":"Place + of Birth","universe":"Persons","nhgisCode":"AFB","sequence":10,"datasetName":"1850_sPAX","nVariables":65},{"name":"NT13","description":"Number + of Families","universe":"Families","nhgisCode":"AFC","sequence":11,"datasetName":"1850_sPAX","nVariables":1},{"name":"NT14","description":"Average + Persons per Family","universe":"Families","nhgisCode":"AFD","sequence":12,"datasetName":"1850_sPAX","nVariables":1},{"name":"NT15","description":"Number + of Slaves per Slave Holder","universe":"Slaveholders","nhgisCode":"AFE","sequence":13,"datasetName":"1850_sPAX","nVariables":11}],"pageNumber":38,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=37\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=39\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1060' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:17 GMT + Etag: + - W/"eed9eff889ba409b4bfd3abfdd133054" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - af3d7e78-0f60-4304-9b99-a3a962fb8620 + X-Runtime: + - '0.009138' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=39&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT16","description":"Total Number of Slave Holders","universe":"Slaveholders","nhgisCode":"AFF","sequence":14,"datasetName":"1850_sPAX","nVariables":1},{"name":"NT17","description":"Employed + Free Males 15 Years of Age and Over by Occupation","universe":"Employed Free + Males 15 Years and Over","nhgisCode":"AFG","sequence":15,"datasetName":"1850_sPAX","nVariables":10},{"name":"NT18","description":"Employed + Free Males Over 15 Years of Age and Over","universe":"Employed Free Males + 15 Years and Over","nhgisCode":"AFH","sequence":16,"datasetName":"1850_sPAX","nVariables":1},{"name":"NT19","description":"Average + Wages by Occupation","universe":"Employed Persons","nhgisCode":"AFI","sequence":17,"datasetName":"1850_sPAX","nVariables":5},{"name":"NT20","description":"Average + Weekly Board to Laboring Men","universe":"Male Laborers","nhgisCode":"AFK","sequence":18,"datasetName":"1850_sPAX","nVariables":1}],"pageNumber":39,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=38\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=40\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1233' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:17 GMT + Etag: + - W/"abb8218f95f8f2d0f4e520e6c95f43cc" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - 2841b75b-e93d-4fc7-bd99-c0698bef07a8 + X-Runtime: + - '0.010751' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=40&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT21","description":"Total Banks and Branches","universe":"Banks + and Branches","nhgisCode":"AFL","sequence":19,"datasetName":"1850_sPAX","nVariables":1},{"name":"NT22","description":"Amount + of Assets in Banks","universe":"Banks","nhgisCode":"AFM","sequence":20,"datasetName":"1850_sPAX","nVariables":4},{"name":"NT23","description":"Total + Money in Circulation","universe":"Banks","nhgisCode":"AFN","sequence":21,"datasetName":"1850_sPAX","nVariables":1},{"name":"NT24","description":"Total + Value of Real Estate and Personal Property","universe":"Real Estate and Personal + Property","nhgisCode":"AFO","sequence":22,"datasetName":"1850_sPAX","nVariables":1},{"name":"NT25","description":"Total + Manufacturing Establishments","universe":"Manufacturing Establishments","nhgisCode":"AFP","sequence":23,"datasetName":"1850_sPAX","nVariables":1}],"pageNumber":40,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=39\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=41\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1164' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:18 GMT + Etag: + - W/"da7b883a71583e7dafc990dd6d95eed1" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - 0f27d82b-20f4-4ddd-b036-c1ebc112cd88 + X-Runtime: + - '0.010057' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=41&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT26","description":"Total Capital Invested in Manufacturing","universe":"Manufacturing + Establishments","nhgisCode":"AFQ","sequence":24,"datasetName":"1850_sPAX","nVariables":1},{"name":"NT27","description":"Average + Number of Persons Over 16 Years of Age Employed in Manufacturing by Sex","universe":"Employed + Persons Over 16 Years of Age in Manufacturing","nhgisCode":"AFR","sequence":25,"datasetName":"1850_sPAX","nVariables":2},{"name":"NT28","description":"Total + Annual Wages in Manufacturing","universe":"Manufacturing Establishments","nhgisCode":"AFS","sequence":26,"datasetName":"1850_sPAX","nVariables":1},{"name":"NT29","description":"Total + Value of Raw Materials Used in Manufacturing","universe":"Manufacturing Establishments","nhgisCode":"AFT","sequence":27,"datasetName":"1850_sPAX","nVariables":1},{"name":"NT30","description":"Total + Value of Products in Manufacturing","universe":"Manufacturing Establishments","nhgisCode":"AFU","sequence":28,"datasetName":"1850_sPAX","nVariables":1}],"pageNumber":41,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=40\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=42\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1327' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:18 GMT + Etag: + - W/"5e2aeff613264f58ee162b8179db4522" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - 8f8bd849-dc96-4bd3-8428-3ff8898c7487 + X-Runtime: + - '0.011594' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=42&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT31","description":"Total Persons and Establishments + Engaged in Manufacturing","universe":"Manufacturing Establishments","nhgisCode":"AFV","sequence":29,"datasetName":"1850_sPAX","nVariables":1},{"name":"NT32","description":"Total + Capital Invested in Manufacturing","universe":"Manufacturing Establishments","nhgisCode":"AFW","sequence":30,"datasetName":"1850_sPAX","nVariables":1},{"name":"NT33","description":"Total + Value of Raw Materials Used in Manufacturing","universe":"Manufacturing Establishments","nhgisCode":"AFX","sequence":31,"datasetName":"1850_sPAX","nVariables":1},{"name":"NT34","description":"Persons + Employed in Manufacturing by Sex","universe":"Employed Persons in Manufacturing + Establishments","nhgisCode":"AFY","sequence":32,"datasetName":"1850_sPAX","nVariables":2},{"name":"NT35","description":"Total + Annual Wages in Manufacturing","universe":"Manufacturing Establishments","nhgisCode":"AFZ","sequence":33,"datasetName":"1850_sPAX","nVariables":1}],"pageNumber":42,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=41\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=43\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1299' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:18 GMT + Etag: + - W/"738a84076654b9028629c869f217f3d4" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - 76dd9ca2-7ce7-4688-b359-9af8b506a227 + X-Runtime: + - '0.009565' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=43&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT36","description":"Total Value of Annual Product + in Manufacturing","universe":"Manufacturing Establishments","nhgisCode":"AF0","sequence":34,"datasetName":"1850_sPAX","nVariables":1},{"name":"NT37","description":"Percent + Profit in Manufacturing","universe":"Manufacturing Establishments","nhgisCode":"AF1","sequence":35,"datasetName":"1850_sPAX","nVariables":1},{"name":"NT38","description":"Total + Number of Farms, Plantations, Etc","universe":"Farms, Plantations, Etc.","nhgisCode":"AF2","sequence":36,"datasetName":"1850_sPAX","nVariables":1},{"name":"NT39","description":"Average + Number of Acres per Farm","universe":"Farms","nhgisCode":"AF3","sequence":37,"datasetName":"1850_sPAX","nVariables":1},{"name":"NT40","description":"Average + Value of Farms","universe":"Farms","nhgisCode":"AF5","sequence":38,"datasetName":"1850_sPAX","nVariables":1}],"pageNumber":43,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=42\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=44\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1178' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:18 GMT + Etag: + - W/"e886c0b91a9cc9095f2fcf905aa73c45" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - f5c3609c-94c3-4062-ad82-7af4ebafe08b + X-Runtime: + - '0.010147' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=44&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT41","description":"Average Value of Farm Implements + and Machinery","universe":"Farms","nhgisCode":"AF6","sequence":39,"datasetName":"1850_sPAX","nVariables":1},{"name":"NT42","description":"Average + Value of Farm, Farm Implements and Machinery","universe":"Farms","nhgisCode":"AF7","sequence":40,"datasetName":"1850_sPAX","nVariables":1},{"name":"NT43","description":"Newspapers + by Frequency of Issue","universe":"Newspapers","nhgisCode":"AF8","sequence":41,"datasetName":"1850_sPAX","nVariables":7},{"name":"NT44","description":"Annual + Number of Copies by Frequency of Newspaper Issue","universe":"Newspapers","nhgisCode":"AF9","sequence":42,"datasetName":"1850_sPAX","nVariables":7},{"name":"NT45","description":"Aggregate + Number of Newspapers","universe":"Newspapers","nhgisCode":"AGA","sequence":43,"datasetName":"1850_sPAX","nVariables":1}],"pageNumber":44,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=43\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=45\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1173' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:18 GMT + Etag: + - W/"24bae9fd69e2f7a948c9118f627a8b94" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - c3c03d5d-39b9-4814-b06d-26f687471a1e + X-Runtime: + - '0.008057' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=45&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT46","description":"Aggregate Annual Number of Copies + of Newspapers","universe":"Newspapers","nhgisCode":"AGB","sequence":44,"datasetName":"1850_sPAX","nVariables":1},{"name":"NT47","description":"Number + of Newspapers by Publication Subject Coverage","universe":"Newspapers","nhgisCode":"AGC","sequence":45,"datasetName":"1850_sPAX","nVariables":5},{"name":"NT48","description":"Circulation + of Newspapers by Publication Subject Coverage","universe":"Newspapers","nhgisCode":"AGD","sequence":46,"datasetName":"1850_sPAX","nVariables":5},{"name":"NT49","description":"Annual + Number of Newspaper Copies by Publication Subject Coverage","universe":"Newspapers","nhgisCode":"AGE","sequence":47,"datasetName":"1850_sPAX","nVariables":5},{"name":"NT50","description":"Aggregate + Circulation of Newspapers","universe":"Newspapers","nhgisCode":"AGG","sequence":48,"datasetName":"1850_sPAX","nVariables":1}],"pageNumber":45,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=44\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=46\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1224' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:18 GMT + Etag: + - W/"c55fde73ac68de9bd80235a260343981" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - 034c6911-509c-4353-aed6-0b8dbb0b0114 + X-Runtime: + - '0.010912' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=46&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT51","description":"Types of Libraries","universe":"Libraries","nhgisCode":"AGH","sequence":49,"datasetName":"1850_sPAX","nVariables":5},{"name":"NT52","description":"Number + of Volumes in Types of Libraries","universe":"Libraries","nhgisCode":"AGI","sequence":50,"datasetName":"1850_sPAX","nVariables":5},{"name":"NT53","description":"Total + Number of Libraries","universe":"Libraries","nhgisCode":"AGJ","sequence":51,"datasetName":"1850_sPAX","nVariables":1},{"name":"NT54","description":"Total + Number of Volumes in Libraries","universe":"Libraries","nhgisCode":"AGK","sequence":52,"datasetName":"1850_sPAX","nVariables":1},{"name":"NT55","description":"Area + (Square Miles)","universe":"Specified Geographic Area","nhgisCode":"AGL","sequence":53,"datasetName":"1850_sPAX","nVariables":1}],"pageNumber":46,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=45\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=47\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1116' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:18 GMT + Etag: + - W/"42a8365f3617dd73ff1ab080b7a48e9b" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - 955daa73-9cbe-43e6-a4c9-7088e357fb47 + X-Runtime: + - '0.009086' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=47&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT001","description":"Average Value of Farmland and + Buildings","universe":"Farms","nhgisCode":"AE7","sequence":1,"datasetName":"1850_1959_cFV","nVariables":1},{"name":"NT1","description":"Improved/Unimproved + Land in Farms","universe":"Farms","nhgisCode":"AGP","sequence":1,"datasetName":"1860_cAg","nVariables":2},{"name":"NT2","description":"Value + of Farms, Implements and Machinery","universe":"Farms","nhgisCode":"AGV","sequence":2,"datasetName":"1860_cAg","nVariables":2},{"name":"NT3","description":"Livestock","universe":"Farms","nhgisCode":"AGW","sequence":3,"datasetName":"1860_cAg","nVariables":7},{"name":"NT4","description":"Total + Value of Livestock","universe":"Farms","nhgisCode":"AGX","sequence":4,"datasetName":"1860_cAg","nVariables":1}],"pageNumber":47,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=46\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=48\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1080' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:18 GMT + Etag: + - W/"5c49c4ade891c4dcac10bf39edb06672" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - b66f99e1-f3dc-443b-9c95-2fbb251319eb + X-Runtime: + - '0.008516' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=48&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT5","description":"Farm Production","universe":"Farms","nhgisCode":"AGY","sequence":5,"datasetName":"1860_cAg","nVariables":33},{"name":"NT6","description":"Value + of Non-Field-Crop Product Groups","universe":"Farms","nhgisCode":"AGZ","sequence":6,"datasetName":"1860_cAg","nVariables":4},{"name":"NT7","description":"Price + of Farm Products, 1859/1860","universe":"Farms","nhgisCode":"AG0","sequence":7,"datasetName":"1860_cAg","nVariables":33},{"name":"NT8","description":"Estimated + Product Output Value by Farm Products","universe":"Farms","nhgisCode":"AG1","sequence":8,"datasetName":"1860_cAg","nVariables":33},{"name":"NT9","description":"Estimated + Value of Products by Farm Product Groups","universe":"Farms","nhgisCode":"AG2","sequence":9,"datasetName":"1860_cAg","nVariables":2}],"pageNumber":48,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=47\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=49\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1114' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:18 GMT + Etag: + - W/"ceca0150b1fbc352262beb3ac76ae045" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - 40272d7d-8875-47a7-8881-36486ea595b4 + X-Runtime: + - '0.008294' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=49&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT10","description":"Estimated Value of Total Agricultural + Output","universe":"Farms","nhgisCode":"AGQ","sequence":10,"datasetName":"1860_cAg","nVariables":1},{"name":"NT11","description":"Farm + Acreage","universe":"Farms of 3 or More Acres","nhgisCode":"AGR","sequence":11,"datasetName":"1860_cAg","nVariables":7},{"name":"NT12","description":"Number + of Slaves Held","universe":"Slaveholders","nhgisCode":"AGS","sequence":12,"datasetName":"1860_cAg","nVariables":21},{"name":"NT13","description":"Total + Slaveholders","universe":"Slaveholders","nhgisCode":"AGT","sequence":13,"datasetName":"1860_cAg","nVariables":1},{"name":"NT14","description":"Total + Number of Slaves","universe":"Slaves","nhgisCode":"AGU","sequence":14,"datasetName":"1860_cAg","nVariables":1}],"pageNumber":49,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=48\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=50\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1090' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:18 GMT + Etag: + - W/"e375976a7f149a6751e8936d3ba91a90" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - 14f69d5b-cb05-41c6-a25b-096021aa4740 + X-Runtime: + - '0.009958' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=50&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT1","description":"Total Population","universe":"Persons","nhgisCode":"AG3","sequence":1,"datasetName":"1860_cPAX","nVariables":1},{"name":"NT2","description":"Urban + Population 2,500 and Over","universe":"Urban Persons (Incorporated Places + 2,500 and Over)","nhgisCode":"AHF","sequence":2,"datasetName":"1860_cPAX","nVariables":1},{"name":"NT3","description":"Population + in Cities of 25,000 and Over","universe":"Persons in Cities of 25,000 and + Over","nhgisCode":"AHP","sequence":3,"datasetName":"1860_cPAX","nVariables":1},{"name":"NT4","description":"Race + and Slave Status by Age by Sex","universe":"Persons","nhgisCode":"AH0","sequence":4,"datasetName":"1860_cPAX","nVariables":180},{"name":"NT5","description":"Race + and Slave Status by Sex","universe":"Persons","nhgisCode":"AH2","sequence":5,"datasetName":"1860_cPAX","nVariables":12}],"pageNumber":50,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=49\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=51\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1167' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:18 GMT + Etag: + - W/"b88835dd0280749a33ffa3e9bd7cd68a" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - 98160b36-112d-4755-9cfe-7bc42fa9b5fe + X-Runtime: + - '0.009582' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=51&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT6","description":"Race and Slave Status","universe":"Persons","nhgisCode":"AH3","sequence":6,"datasetName":"1860_cPAX","nVariables":6},{"name":"NT7","description":"Nativity + by Race by Sex","universe":"Persons","nhgisCode":"AH4","sequence":7,"datasetName":"1860_cPAX","nVariables":12},{"name":"NT8","description":"Nativity + by Race","universe":"Persons","nhgisCode":"AH5","sequence":8,"datasetName":"1860_cPAX","nVariables":6},{"name":"NT9A","description":"Free + Population by Nativity","universe":"Free Persons","nhgisCode":"AH6","sequence":9,"datasetName":"1860_cPAX","nVariables":2},{"name":"NT9B","description":"Aggregate + Free Population","universe":"Free Persons","nhgisCode":"AH7","sequence":10,"datasetName":"1860_cPAX","nVariables":1}],"pageNumber":51,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=50\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=52\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1069' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:19 GMT + Etag: + - W/"86b06681fb1ba4d34bc43cb7503730d0" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - 6a2fcf58-3d75-4180-8e34-a6f491cd3a5d + X-Runtime: + - '0.007373' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=52&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT10","description":"Improved/Unimproved Land in + Farms","universe":"Farms","nhgisCode":"AG4","sequence":11,"datasetName":"1860_cPAX","nVariables":2},{"name":"NT11","description":"Cash + Value of Farms","universe":"Farms","nhgisCode":"AG5","sequence":12,"datasetName":"1860_cPAX","nVariables":1},{"name":"NT12A","description":"Farm + Value by Farm Assets","universe":"Farms","nhgisCode":"AG6","sequence":13,"datasetName":"1860_cPAX","nVariables":2},{"name":"NT12B","description":"Farm + Value by Farm Productions","universe":"Farms","nhgisCode":"AG7","sequence":14,"datasetName":"1860_cPAX","nVariables":4},{"name":"NT13","description":"Farm + Size","universe":"Farms 3 Acres and Over","nhgisCode":"AG8","sequence":15,"datasetName":"1860_cPAX","nVariables":7}],"pageNumber":52,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=51\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=53\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1078' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:19 GMT + Etag: + - W/"8509f7f741f354d22b8fa3e64378ea17" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - 0a6e7f49-431c-4c11-83c5-92d6616315a4 + X-Runtime: + - '0.010151' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=53&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT14","description":"Slave Holders by Number of Slaves","universe":"Slaveholders","nhgisCode":"AG9","sequence":16,"datasetName":"1860_cPAX","nVariables":21},{"name":"NT15","description":"Total + Slave Holders","universe":"Slaveholders","nhgisCode":"AHA","sequence":17,"datasetName":"1860_cPAX","nVariables":1},{"name":"NT16","description":"Slave + Status","universe":"Persons","nhgisCode":"AHB","sequence":18,"datasetName":"1860_cPAX","nVariables":2},{"name":"NT17","description":"Manufacturing + Establishments","universe":"Manufacturing Establishments","nhgisCode":"AHC","sequence":19,"datasetName":"1860_cPAX","nVariables":1},{"name":"NT18","description":"Capital + Invested in Manufacturing","universe":"Manufacturing Establishments","nhgisCode":"AHD","sequence":20,"datasetName":"1860_cPAX","nVariables":1}],"pageNumber":53,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=52\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=54\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1131' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:19 GMT + Etag: + - W/"ee2fb5cca347c306fa4b9e54be9ac1ee" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - 3b0dde8b-6d26-4a6c-8d04-905de3e36ca9 + X-Runtime: + - '0.009542' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=54&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT19","description":"Cost of Raw Materials in Manufacturing","universe":"Manufacturing + Establishments","nhgisCode":"AHE","sequence":21,"datasetName":"1860_cPAX","nVariables":1},{"name":"NT20","description":"Population + Employed in Manufacturing by Sex","universe":"Employed Persons in Manufacturing + Establishments","nhgisCode":"AHG","sequence":22,"datasetName":"1860_cPAX","nVariables":2},{"name":"NT21","description":"Annual + Cost of Labor in Manufacturing","universe":"Manufacturing Establishments","nhgisCode":"AHH","sequence":23,"datasetName":"1860_cPAX","nVariables":1},{"name":"NT22","description":"Annual + Value of Products in Manufacturing","universe":"Manufacturing Establishments","nhgisCode":"AHI","sequence":24,"datasetName":"1860_cPAX","nVariables":1},{"name":"NT23","description":"True + Value by Property Type","universe":"Estate Property","nhgisCode":"AHJ","sequence":25,"datasetName":"1860_cPAX","nVariables":2}],"pageNumber":54,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=53\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=55\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1251' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:19 GMT + Etag: + - W/"54ed3ae5f5fa914c3de715c26a01d703" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - 93c9bbad-c3dd-4b48-9bcc-41eb8f144d11 + X-Runtime: + - '0.008778' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=55&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT24","description":"Families","universe":"Families","nhgisCode":"AHK","sequence":26,"datasetName":"1860_cPAX","nVariables":1},{"name":"NT26","description":"Churches + by Denomination","universe":"Churches","nhgisCode":"AHL","sequence":27,"datasetName":"1860_cPAX","nVariables":22},{"name":"NT27","description":"Churches + by Selected Detailed Denomination","universe":"Churches","nhgisCode":"AHM","sequence":28,"datasetName":"1860_cPAX","nVariables":9},{"name":"NT28","description":"Aggregate + Accommodations of Churches by Denomination","universe":"Church Accommodations","nhgisCode":"AHN","sequence":29,"datasetName":"1860_cPAX","nVariables":22},{"name":"NT29","description":"Aggregate + Accommodations of Churches by Selected Detailed Denomination","universe":"Church + Accommodations","nhgisCode":"AHO","sequence":30,"datasetName":"1860_cPAX","nVariables":9}],"pageNumber":55,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=54\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=56\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1182' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:19 GMT + Etag: + - W/"2284c5bfbe76ed8143e250e612596106" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - 8c989d7b-9125-4b84-b361-6f35eda1f810 + X-Runtime: + - '0.009492' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=56&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT30","description":"Value of Church Property by + Denomination","universe":"Church Property","nhgisCode":"AHQ","sequence":31,"datasetName":"1860_cPAX","nVariables":22},{"name":"NT31","description":"Value + of Church Property by Selected Detailed Denomination","universe":"Church Property","nhgisCode":"AHR","sequence":32,"datasetName":"1860_cPAX","nVariables":9},{"name":"NT32","description":"Total + Churches","universe":"Churches","nhgisCode":"AHS","sequence":33,"datasetName":"1860_cPAX","nVariables":1},{"name":"NT33","description":"Total + Aggregate Accommodations of Churches","universe":"Church Accommodations","nhgisCode":"AHT","sequence":34,"datasetName":"1860_cPAX","nVariables":1},{"name":"NT34","description":"Total + Value of Church Property","universe":"Church Property","nhgisCode":"AHU","sequence":35,"datasetName":"1860_cPAX","nVariables":1}],"pageNumber":56,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=55\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=57\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1177' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:19 GMT + Etag: + - W/"5d03752479e85a41699c356f33dbb5e8" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - 3a2eca10-f57d-4617-adae-53b4d1c41d77 + X-Runtime: + - '0.007993' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=57&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT35","description":"Sex by Age","universe":"Persons","nhgisCode":"AHV","sequence":36,"datasetName":"1860_cPAX","nVariables":30},{"name":"NT36","description":"Sex","universe":"Persons","nhgisCode":"AHW","sequence":37,"datasetName":"1860_cPAX","nVariables":2},{"name":"NT37","description":"Age","universe":"Persons","nhgisCode":"AHX","sequence":38,"datasetName":"1860_cPAX","nVariables":15},{"name":"NT38","description":"Population + 80 Years of Age and Over by Sex","universe":"Persons 80 Years and Over","nhgisCode":"AHY","sequence":39,"datasetName":"1860_cPAX","nVariables":2},{"name":"NT39","description":"Water + Transport","universe":"Specified Geographic Area","nhgisCode":"AHZ","sequence":40,"datasetName":"1860_cPAX","nVariables":1}],"pageNumber":57,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=56\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=58\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1064' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:19 GMT + Etag: + - W/"7908632291ed27fc61c509190a9c9303" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - 402741d5-28c6-42e0-b1da-181237e5e411 + X-Runtime: + - '0.010020' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=58&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT40","description":"Rail Transport","universe":"Specified + Geographic Area","nhgisCode":"AH1","sequence":41,"datasetName":"1860_cPAX","nVariables":1},{"name":"NT1A","description":"Place + of Birth","universe":"Persons","nhgisCode":"AII","sequence":1,"datasetName":"1860_sPAX","nVariables":74},{"name":"NT1B","description":"Persons + Born in Germany by German State","universe":"Persons Born in Germany","nhgisCode":"AIJ","sequence":2,"datasetName":"1860_sPAX","nVariables":7},{"name":"NT2","description":"Nativity","universe":"Persons","nhgisCode":"AIK","sequence":3,"datasetName":"1860_sPAX","nVariables":2},{"name":"NT4","description":"Place + of Birth","universe":"Persons","nhgisCode":"AI5","sequence":4,"datasetName":"1860_sPAX","nVariables":5}],"pageNumber":58,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=57\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=59\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1071' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:19 GMT + Etag: + - W/"b00dc65a09aaf4668aa27e23a8198296" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - 0dd5e276-0c25-4d3d-bf5a-7f9fd84ce715 + X-Runtime: + - '0.008626' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=59&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT5","description":"Total Free Population","universe":"Free + Persons","nhgisCode":"AJG","sequence":5,"datasetName":"1860_sPAX","nVariables":1},{"name":"NT6","description":"Total + Acres of Land Not in Farms","universe":"Farms","nhgisCode":"AJN","sequence":6,"datasetName":"1860_sPAX","nVariables":1},{"name":"NT7","description":"Total + Land Area in Acres","universe":"Specified Geographic Area","nhgisCode":"AJO","sequence":7,"datasetName":"1860_sPAX","nVariables":1},{"name":"NT8","description":"Total + Land Area in Square Miles","universe":"Specified Geographic Area","nhgisCode":"AJP","sequence":8,"datasetName":"1860_sPAX","nVariables":1},{"name":"NT9","description":"Number + of Inhabitants per Square Mile","universe":"Persons","nhgisCode":"AJQ","sequence":9,"datasetName":"1860_sPAX","nVariables":1}],"pageNumber":59,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=58\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=60\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1127' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:19 GMT + Etag: + - W/"f547bf3f7b0b88477ab8f9a666fac3fe" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - 47df4646-2bff-4684-9091-4e1c94047605 + X-Runtime: + - '0.008833' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=60&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT10","description":"Total Dwellings","universe":"Dwellings","nhgisCode":"AH8","sequence":10,"datasetName":"1860_sPAX","nVariables":1},{"name":"NT11","description":"Average + Number of Persons to a Dwelling","universe":"Persons","nhgisCode":"AH9","sequence":11,"datasetName":"1860_sPAX","nVariables":1},{"name":"NT12","description":"Number + of Family Members","universe":"Families","nhgisCode":"AIA","sequence":12,"datasetName":"1860_sPAX","nVariables":2},{"name":"NT13","description":"Average + Number of Persons per Family","universe":"Families","nhgisCode":"AIB","sequence":13,"datasetName":"1860_sPAX","nVariables":1},{"name":"NT14","description":"Deaths + by Sex","universe":"Deaths","nhgisCode":"AIC","sequence":14,"datasetName":"1860_sPAX","nVariables":2}],"pageNumber":60,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=59\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=61\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1083' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:19 GMT + Etag: + - W/"42798acc4dc7ef002a5889af54ee2952" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - 278cec68-98f9-493f-bbce-1475a80e5424 + X-Runtime: + - '0.007278' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=61&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT15","description":"Total Deaths","universe":"Deaths","nhgisCode":"AID","sequence":15,"datasetName":"1860_sPAX","nVariables":1},{"name":"NT16","description":"Average + Monthly Wages to a Farm Hand with Board","universe":"Farm Hands with Board","nhgisCode":"AIE","sequence":16,"datasetName":"1860_sPAX","nVariables":1},{"name":"NT17","description":"Average + Wages to Day Laborer by Boarding Status","universe":"Day Laborers","nhgisCode":"AIF","sequence":17,"datasetName":"1860_sPAX","nVariables":2},{"name":"NT18","description":"Average + Day Wages to a Carpenter without Board","universe":"Carpenters without Board","nhgisCode":"AIG","sequence":18,"datasetName":"1860_sPAX","nVariables":1},{"name":"NT19","description":"Average + Weekly Wages for a Female Domestic, with Board","universe":"Female Domestics + with Board","nhgisCode":"AIH","sequence":19,"datasetName":"1860_sPAX","nVariables":1}],"pageNumber":61,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=60\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=62\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1214' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:19 GMT + Etag: + - W/"aa658007c29495ecbe03f9dca3f69c3c" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - d951a414-24b4-4134-9145-756296b0ff62 + X-Runtime: + - '0.011391' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=62&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT20","description":"Price of Board per Week to Laboring + Men","universe":"Male Laborers","nhgisCode":"AIL","sequence":20,"datasetName":"1860_sPAX","nVariables":1},{"name":"NT21","description":"Amount + of Annual Taxes by Type of Tax","universe":"Specified Geographic Area","nhgisCode":"AIM","sequence":21,"datasetName":"1860_sPAX","nVariables":8},{"name":"NT22","description":"Amount + of Taxes by Payment Method","universe":"Specified Geographic Area","nhgisCode":"AIN","sequence":22,"datasetName":"1860_sPAX","nVariables":2},{"name":"NT23","description":"Total + Annual Taxes","universe":"Specified Geographic Area","nhgisCode":"AIO","sequence":23,"datasetName":"1860_sPAX","nVariables":1},{"name":"NT24","description":"Total + Banks and Branches","universe":"Banks and Branches","nhgisCode":"AIP","sequence":24,"datasetName":"1860_sPAX","nVariables":1}],"pageNumber":62,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=61\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=63\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1175' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:20 GMT + Etag: + - W/"cf6c2162ed12470c848ed4095abf73a7" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - b381b9e7-d9e7-4cc3-a9db-92100c95cd3c + X-Runtime: + - '0.009845' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=63&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT25","description":"Amount of Bank Assets","universe":"Banks","nhgisCode":"AIQ","sequence":25,"datasetName":"1860_sPAX","nVariables":4},{"name":"NT26","description":"Total + Amount of Money in Circulation","universe":"Specified Geographic Area","nhgisCode":"AIR","sequence":26,"datasetName":"1860_sPAX","nVariables":1},{"name":"NT27","description":"Assessed + Value of Property by Property Type","universe":"Property","nhgisCode":"AIS","sequence":27,"datasetName":"1860_sPAX","nVariables":2},{"name":"NT28","description":"Total + True Value of Real Estate and Personal Property","universe":"Real Estate and + Personal Property","nhgisCode":"AIT","sequence":28,"datasetName":"1860_sPAX","nVariables":1},{"name":"NT29","description":"Number + of Miles of Railroads in Operation by Year","universe":"Railroads in Operation","nhgisCode":"AIU","sequence":29,"datasetName":"1860_sPAX","nVariables":11}],"pageNumber":63,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=62\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=64\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1214' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:20 GMT + Etag: + - W/"f80e730844db395419ae463116152437" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - 75a0e5c1-c5f6-42f9-8506-827445654ef1 + X-Runtime: + - '0.010200' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=64&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT30","description":"Total Farms","universe":"Farms","nhgisCode":"AIV","sequence":30,"datasetName":"1860_sPAX","nVariables":1},{"name":"NT31","description":"Improved/Unimproved + Land in Farms","universe":"Farms","nhgisCode":"AIW","sequence":31,"datasetName":"1860_sPAX","nVariables":2},{"name":"NT32","description":"Average + Acres of Land per Farm","universe":"Farms","nhgisCode":"AIX","sequence":32,"datasetName":"1860_sPAX","nVariables":1},{"name":"NT33","description":"Newspapers + by Frequency of Issue by Publication Subject Coverage","universe":"Political + and Miscellaneous Newspapers","nhgisCode":"AIY","sequence":33,"datasetName":"1860_sPAX","nVariables":14},{"name":"NT34","description":"Religious + Newspapers by Frequency of Issue","universe":"Religious Newspapers","nhgisCode":"AIZ","sequence":34,"datasetName":"1860_sPAX","nVariables":4}],"pageNumber":64,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=63\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=65\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1172' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:20 GMT + Etag: + - W/"dbaca64e958f3982e6ada52705a0626d" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - 6eb94d45-2011-4422-81b8-f2e51410b27e + X-Runtime: + - '0.009384' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=65&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT35","description":"Literary Newspapers by Frequency + of Issue","universe":"Literary Newspapers","nhgisCode":"AI0","sequence":35,"datasetName":"1860_sPAX","nVariables":5},{"name":"NT36","description":"Total + Number of Newspapers by Publication Subject Coverage","universe":"Newspapers","nhgisCode":"AI1","sequence":36,"datasetName":"1860_sPAX","nVariables":4},{"name":"NT37","description":"Number + of Copies of Newspapers by Frequency of Issue","universe":"Newspapers","nhgisCode":"AI2","sequence":37,"datasetName":"1860_sPAX","nVariables":7},{"name":"NT38","description":"Total + Number of Copies of All Newspapers Annually","universe":"Newspapers","nhgisCode":"AI3","sequence":38,"datasetName":"1860_sPAX","nVariables":1},{"name":"NT39","description":"Persons + Attending School by Race by Sex","universe":"Persons Attending School","nhgisCode":"AI4","sequence":39,"datasetName":"1860_sPAX","nVariables":4}],"pageNumber":65,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=64\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=66\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1230' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:20 GMT + Etag: + - W/"4e50bcd4bcbfacfd00e844a80f7acf37" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - 49145e20-6c95-44b3-bc18-a330a50c781c + X-Runtime: + - '0.008112' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=66&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT40","description":"Persons Attending School by + Race","universe":"Persons Attending School","nhgisCode":"AI6","sequence":40,"datasetName":"1860_sPAX","nVariables":2},{"name":"NT41","description":"Persons + Attending School by Nativity","universe":"Persons Attending School","nhgisCode":"AI7","sequence":41,"datasetName":"1860_sPAX","nVariables":2},{"name":"NT42","description":"Total + Number of Persons Attending School","universe":"Persons Attending School","nhgisCode":"AI8","sequence":42,"datasetName":"1860_sPAX","nVariables":1},{"name":"NT43","description":"Free + Persons Over 20 Years of Age Who Cannot Read and Write by Race by Sex","universe":"Free + Persons Over 20 Years of Age Who Cannot Read and Write","nhgisCode":"AI9","sequence":43,"datasetName":"1860_sPAX","nVariables":4},{"name":"NT44","description":"Free + Persons Over 20 Years of Age Who Cannot Read and Write by Race","universe":"Free + Persons Over 20 Years of Age Who Cannot Read and Write","nhgisCode":"AJA","sequence":44,"datasetName":"1860_sPAX","nVariables":2}],"pageNumber":66,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=65\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=67\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1357' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:20 GMT + Etag: + - W/"76547f28905e3de0be7ecc53b6e8b2c3" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - 24541cd9-ac6a-4d79-bfc5-1a8682d028f6 + X-Runtime: + - '0.010377' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=67&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT45","description":"Persons Over 20 Years of Age + Who Cannot Read and Write by Nativity","universe":"Persons Over 20 Years of + Age Who Cannot Read and Write","nhgisCode":"AJB","sequence":45,"datasetName":"1860_sPAX","nVariables":2},{"name":"NT46","description":"Total + Persons Over 20 Years of Age Who Cannot Read and Write","universe":"Persons + Over 20 Years of Age Who Cannot Read and Write","nhgisCode":"AJC","sequence":46,"datasetName":"1860_sPAX","nVariables":1},{"name":"NT47","description":"Number + of Schools by Type of School","universe":"Schools","nhgisCode":"AJD","sequence":47,"datasetName":"1860_sPAX","nVariables":8},{"name":"NT48","description":"Number + of Libraries by Type of Library","universe":"Libraries","nhgisCode":"AJE","sequence":48,"datasetName":"1860_sPAX","nVariables":5},{"name":"NT49","description":"Number + of Volumes in Libraries by Type of Library","universe":"Libraries","nhgisCode":"AJF","sequence":49,"datasetName":"1860_sPAX","nVariables":5}],"pageNumber":67,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=66\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=68\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1299' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:20 GMT + Etag: + - W/"7a7aae10b6d32d90acd008a1f13a211b" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - c0b1bb8e-7c79-42b3-b601-bdfd89bc7dea + X-Runtime: + - '0.007664' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=68&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT50","description":"Total Number of Libraries","universe":"Libraries","nhgisCode":"AJH","sequence":50,"datasetName":"1860_sPAX","nVariables":1},{"name":"NT51","description":"Total + Number of Volumes in Libraries","universe":"Libraries","nhgisCode":"AJI","sequence":51,"datasetName":"1860_sPAX","nVariables":1},{"name":"NT52","description":"Type + of Learning Institution","universe":"Learning Institutions","nhgisCode":"AJJ","sequence":52,"datasetName":"1860_sPAX","nVariables":3},{"name":"NT53","description":"Type + of Learning Institution by Persons in Learning Institutions","universe":"Learning + Institutions","nhgisCode":"AJK","sequence":53,"datasetName":"1860_sPAX","nVariables":6},{"name":"NT55","description":"Annual + Income by Type of Learning Institution by Income Source","universe":"Learning + Institutions","nhgisCode":"AJL","sequence":54,"datasetName":"1860_sPAX","nVariables":12}],"pageNumber":68,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=67\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=69\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1215' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:20 GMT + Etag: + - W/"4b0695e39188d33f0684c1dc9123e5a1" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - 8a3d2f19-963b-4227-ab8f-52cdbf535e7c + X-Runtime: + - '0.009939' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=69&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT56","description":"Total Annual Income by Type + of Learning Institution","universe":"Learning Institutions","nhgisCode":"AJM","sequence":55,"datasetName":"1860_sPAX","nVariables":3},{"name":"NT001","description":"Marriages + [1867-1886, 1916, 1922-1932, 1949-1950, 1952, 1957-1987, 2000, 2010]","universe":"Marriages","nhgisCode":"AITZ","sequence":1,"datasetName":"1867_2010_cMD","nVariables":1},{"name":"NT002","description":"Divorces + [1867-1886, 1890, 1900, 1916, 1922-1932, 1950, 1952, 1957-1987, 2000, 2010]","universe":"Divorces","nhgisCode":"AIT0","sequence":2,"datasetName":"1867_2010_cMD","nVariables":1},{"name":"NT1","description":"Total + Population","universe":"Persons","nhgisCode":"AJR","sequence":1,"datasetName":"1870_cAg","nVariables":1},{"name":"NT2","description":"Improved/Unimproved + Land in Farms","universe":"Farms","nhgisCode":"AJU","sequence":2,"datasetName":"1870_cAg","nVariables":3}],"pageNumber":69,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=68\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=70\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1234' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:20 GMT + Etag: + - W/"b9db61dd4f422871ff89db9e48c740f3" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - bae96368-2052-44db-86b4-69b7a3ec0b61 + X-Runtime: + - '0.008916' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=70&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT3","description":"Value of Farms, Implements and + Machinery","universe":"Farms","nhgisCode":"AJV","sequence":3,"datasetName":"1870_cAg","nVariables":2},{"name":"NT4","description":"Total + Annual Agricultural Wages Paid","universe":"Farms","nhgisCode":"AJW","sequence":4,"datasetName":"1870_cAg","nVariables":1},{"name":"NT5","description":"Total + Estimated Value of All Farm Productions","universe":"Farms","nhgisCode":"AJX","sequence":5,"datasetName":"1870_cAg","nVariables":1},{"name":"NT6","description":"Estimated + Value of Non-Field-Crop Products","universe":"Farms","nhgisCode":"AJY","sequence":6,"datasetName":"1870_cAg","nVariables":5},{"name":"NT7","description":"Total + Value of All Livestock","universe":"Farms","nhgisCode":"AJZ","sequence":7,"datasetName":"1870_cAg","nVariables":1}],"pageNumber":70,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=69\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=71\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1119' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:20 GMT + Etag: + - W/"c474874e258f22215b7ab95344240eb2" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - e4eb4dbb-f1ce-42fc-8a5b-2b78a8999e6a + X-Runtime: + - '0.009695' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=71&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT8","description":"Livestock","universe":"Farms","nhgisCode":"AJ0","sequence":8,"datasetName":"1870_cAg","nVariables":7},{"name":"NT9","description":"Farm + Productions","universe":"Farms","nhgisCode":"AJ1","sequence":9,"datasetName":"1870_cAg","nVariables":34},{"name":"NT10","description":"Number + of Farms","universe":"Farms","nhgisCode":"AJS","sequence":10,"datasetName":"1870_cAg","nVariables":1},{"name":"NT11","description":"Farm + Acreage","universe":"Farms","nhgisCode":"AJT","sequence":11,"datasetName":"1870_cAg","nVariables":8},{"name":"NT1","description":"Total + Population","universe":"Persons","nhgisCode":"ANU","sequence":1,"datasetName":"1870_cMfg","nVariables":1}],"pageNumber":71,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=70\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=72\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1004' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:20 GMT + Etag: + - W/"38e440fdbfe835de1092bbf038d46b8d" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - 8604537c-56c3-407d-b461-6c63de5516b6 + X-Runtime: + - '0.009222' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=72&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT2","description":"Manufacturing Establishments","universe":"Manufacturing + Establishments","nhgisCode":"ANX","sequence":2,"datasetName":"1870_cMfg","nVariables":1},{"name":"NT3","description":"Persons + Employed in Manufacturing","universe":"Employed Persons in Manufacturing Establishments","nhgisCode":"ANY","sequence":3,"datasetName":"1870_cMfg","nVariables":1},{"name":"NT4","description":"Capital + Invested in Manufacturing","universe":"Manufacturing Establishments","nhgisCode":"ANZ","sequence":4,"datasetName":"1870_cMfg","nVariables":1},{"name":"NT5","description":"Total + Annual Wages in Manufacturing","universe":"Manufacturing Establishments","nhgisCode":"AN0","sequence":5,"datasetName":"1870_cMfg","nVariables":1},{"name":"NT6","description":"Sex","universe":"Employed + Persons 16 Years and Over in Manufacturing","nhgisCode":"AN1","sequence":6,"datasetName":"1870_cMfg","nVariables":2}],"pageNumber":72,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=71\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=73\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1223' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:20 GMT + Etag: + - W/"4a8c5fe28ad56ba3a4e8cbd0f15b3e03" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - 0286f0fe-5fab-4ff7-b87d-5d364f12bed2 + X-Runtime: + - '0.009576' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=73&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT7","description":"Persons Under 16 Years Employed + in Manufacturing","universe":"Employed Persons Under 16 Years of Age in Manufacturing","nhgisCode":"AN2","sequence":7,"datasetName":"1870_cMfg","nVariables":1},{"name":"NT8","description":"Power + Sources Used in Manufacturing","universe":"Manufacturing Establishments","nhgisCode":"AN3","sequence":8,"datasetName":"1870_cMfg","nVariables":2},{"name":"NT9","description":"Horsepower + Produced by Power Sources Used in Manufacturing","universe":"Manufacturing + Establishments","nhgisCode":"AN4","sequence":9,"datasetName":"1870_cMfg","nVariables":2},{"name":"NT10","description":"Value + of Materials Used in Manufacturing","universe":"Manufacturing Establishments","nhgisCode":"ANV","sequence":10,"datasetName":"1870_cMfg","nVariables":1},{"name":"NT11","description":"Value + of Manufacturing Output","universe":"Manufacturing Establishments","nhgisCode":"ANW","sequence":11,"datasetName":"1870_cMfg","nVariables":1}],"pageNumber":73,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=72\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=74\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1289' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:20 GMT + Etag: + - W/"92885414fed35e1ea6bdbbd1818d04e0" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - 70841b3d-bad1-4f4f-8d4d-24f30cb2f433 + X-Runtime: + - '0.009283' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=74&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT1","description":"Total Population","universe":"Persons","nhgisCode":"AJ3","sequence":1,"datasetName":"1870_cPAX","nVariables":1},{"name":"NT2","description":"Urban + Population 2,500 and Over","universe":"Urban Persons (Incorporated Places + 2,500 and Over)","nhgisCode":"AKE","sequence":2,"datasetName":"1870_cPAX","nVariables":1},{"name":"NT3","description":"Total + Population in Cities of 25,000 and over","universe":"Persons in Cities of + 25,000 and Over","nhgisCode":"AKO","sequence":3,"datasetName":"1870_cPAX","nVariables":1},{"name":"NT4","description":"Race","universe":"Persons","nhgisCode":"AK3","sequence":4,"datasetName":"1870_cPAX","nVariables":4},{"name":"NT5","description":"Nativity","universe":"Persons","nhgisCode":"ALB","sequence":5,"datasetName":"1870_cPAX","nVariables":2}],"pageNumber":74,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=73\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=75\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1119' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:21 GMT + Etag: + - W/"1e6d226fa7c25cbe88057797908e08fb" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - 052e670e-f67a-46f2-8ef8-8754b588fa54 + X-Runtime: + - '0.010176' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=75&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT6A","description":"Population with One or Both + Parents Foreign-Born","universe":"Persons with One or Both Parents Foreign-Born","nhgisCode":"ALC","sequence":6,"datasetName":"1870_cPAX","nVariables":1},{"name":"NT6B","description":"Population + with One or Both Parents Foreign-Born by Nativity of Parents","universe":"Persons + with One or Both Parents Foreign-Born","nhgisCode":"ALD","sequence":7,"datasetName":"1870_cPAX","nVariables":3},{"name":"NT7","description":"Nativity","universe":"Persons","nhgisCode":"ALE","sequence":8,"datasetName":"1870_cPAX","nVariables":2},{"name":"NT8","description":"Place + of Birth","universe":"Persons","nhgisCode":"ALF","sequence":9,"datasetName":"1870_cPAX","nVariables":77},{"name":"NT9","description":"Total + Persons Attending School","universe":"Persons Attending School","nhgisCode":"ALG","sequence":10,"datasetName":"1870_cPAX","nVariables":1}],"pageNumber":75,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=74\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=76\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1211' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:21 GMT + Etag: + - W/"39afc9848656e7929d9e395823c76a64" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - f6df2b7a-5ad4-432f-9d4f-f96350f4b2f2 + X-Runtime: + - '0.008027' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=76&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT10","description":"Persons Attending School by + Nativity","universe":"Persons Attending School","nhgisCode":"AJ4","sequence":11,"datasetName":"1870_cPAX","nVariables":2},{"name":"NT11","description":"Persons + Attending School by Race by Sex","universe":"Persons Attending School","nhgisCode":"AJ5","sequence":12,"datasetName":"1870_cPAX","nVariables":4},{"name":"NT12","description":"Persons + Attending School by Race","universe":"Persons Attending School","nhgisCode":"AJ6","sequence":13,"datasetName":"1870_cPAX","nVariables":3},{"name":"NT13","description":"Total + Persons 10 Years of Age and Over Who Cannot Read","universe":"Persons 10 Years + and Over Who Cannot Read","nhgisCode":"AJ7","sequence":14,"datasetName":"1870_cPAX","nVariables":1},{"name":"NT14","description":"Total + Persons Who Cannot Write","universe":"Persons Who Cannot Write","nhgisCode":"AJ8","sequence":15,"datasetName":"1870_cPAX","nVariables":1}],"pageNumber":76,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=75\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=77\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1246' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:21 GMT + Etag: + - W/"e4882d13a0c3e229856efe76179d76a6" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - 4bed46af-a6e1-447b-9a5f-0f0e39650355 + X-Runtime: + - '0.008810' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=77&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT15","description":"Persons Who Cannot Write by + Nativity","universe":"Persons Who Cannot Write","nhgisCode":"AJ9","sequence":16,"datasetName":"1870_cPAX","nVariables":2},{"name":"NT16","description":"Persons + Who Cannot Write by Race by Age by Sex","universe":"Persons Who Cannot Write","nhgisCode":"AKA","sequence":17,"datasetName":"1870_cPAX","nVariables":12},{"name":"NT17","description":"Persons + 21 Years of Age and Over Who Cannot Write by Race","universe":"Indians and + Chinese Persons 21 Years and Over Who Cannot Write","nhgisCode":"AKB","sequence":18,"datasetName":"1870_cPAX","nVariables":2},{"name":"NT18","description":"Total + Population","universe":"Persons","nhgisCode":"AKC","sequence":19,"datasetName":"1870_cPAX","nVariables":1},{"name":"NT19","description":"Sex","universe":"Persons","nhgisCode":"AKD","sequence":20,"datasetName":"1870_cPAX","nVariables":2}],"pageNumber":77,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=76\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=78\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1201' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:21 GMT + Etag: + - W/"06fef6c669463f1a55d185c20ff73903" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - b5c17ccd-0a36-47e1-be3c-4f3b53f0e02e + X-Runtime: + - '0.010088' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=78&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT20","description":"Population 5 to 18 Years of + Age by Sex","universe":"Persons 5 to 18 Years of Age","nhgisCode":"AKF","sequence":21,"datasetName":"1870_cPAX","nVariables":2},{"name":"NT21","description":"Male + Population 18 to 44 Years of Age","universe":"Males 18 to 44 Years of Age","nhgisCode":"AKG","sequence":22,"datasetName":"1870_cPAX","nVariables":1},{"name":"NT22","description":"Male + Population 21 Years of Age and Over","universe":"Males 21 Years and Over","nhgisCode":"AKH","sequence":23,"datasetName":"1870_cPAX","nVariables":1},{"name":"NT23","description":"Male + Citizens 21 Years of Age and Over","universe":"Male Citizens 21 Years and + Over","nhgisCode":"AKI","sequence":24,"datasetName":"1870_cPAX","nVariables":1},{"name":"NT24","description":"Farmland + by Improved/Unimproved Land in Farms","universe":"Farms","nhgisCode":"AKJ","sequence":25,"datasetName":"1870_cPAX","nVariables":3}],"pageNumber":78,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=77\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=79\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1230' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:21 GMT + Etag: + - W/"efec20abb0e3cfde29fa9a0eb0668f01" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - f9d7f743-bae0-4e6c-b7c7-766beac927cd + X-Runtime: + - '0.009152' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=79&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT25","description":"Total Present Cash Value of + Farms","universe":"Farms","nhgisCode":"AKK","sequence":26,"datasetName":"1870_cPAX","nVariables":1},{"name":"NT26","description":"Total + Present Cash Value of Farming Implements and Machinery","universe":"Farms","nhgisCode":"AKL","sequence":27,"datasetName":"1870_cPAX","nVariables":1},{"name":"NT27","description":"Total + Annual Agricultural Wages Paid, Including Value of Board.","universe":"Farms","nhgisCode":"AKM","sequence":28,"datasetName":"1870_cPAX","nVariables":1},{"name":"N28","description":"Total + Estimated Value of All Farm Productions, Including Betterments and Addition + to Stock","universe":"Farm Productions","nhgisCode":"AJ2","sequence":29,"datasetName":"1870_cPAX","nVariables":1},{"name":"NT29","description":"Value + of Farm Productions","universe":"Farm Productions","nhgisCode":"AKN","sequence":30,"datasetName":"1870_cPAX","nVariables":6}],"pageNumber":79,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=78\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=80\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1235' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:21 GMT + Etag: + - W/"58e5739d893f705e595bf0af265d459a" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - 5463ba49-3335-497f-ac96-a71bcdd5310b + X-Runtime: + - '0.009916' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=80&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT30","description":"Total Number of Farms","universe":"Farms","nhgisCode":"AKP","sequence":31,"datasetName":"1870_cPAX","nVariables":1},{"name":"NT31","description":"Farms + by Farm Size","universe":"Farms","nhgisCode":"AKQ","sequence":32,"datasetName":"1870_cPAX","nVariables":8},{"name":"NT32","description":"Total + Manufacturing Establishments","universe":"Manufacturing Establishments","nhgisCode":"AKR","sequence":33,"datasetName":"1870_cPAX","nVariables":1},{"name":"NT33","description":"Total + Hands Employed in Manufacturing","universe":"Hands Employed in Manufacturing","nhgisCode":"AKS","sequence":34,"datasetName":"1870_cPAX","nVariables":1},{"name":"NT34","description":"Persons + Over 16 Years of Age Employed in Manufacturing by Sex","universe":"Employed + Persons Over 16 Years of Age in Manufacturing","nhgisCode":"AKT","sequence":35,"datasetName":"1870_cPAX","nVariables":2}],"pageNumber":80,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=79\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=81\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1212' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:21 GMT + Etag: + - W/"ba14b42788f949c36fc87ea02a487d7c" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - 597ee92f-044d-4682-a22c-51ad2c96d856 + X-Runtime: + - '0.008915' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=81&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT35","description":"Total Youths Employed in Manufacturing","universe":"Employed + Youths in Manufacturing","nhgisCode":"AKU","sequence":36,"datasetName":"1870_cPAX","nVariables":1},{"name":"NT36A","description":"Capital + Invested in Manufacturing","universe":"Manufacturing Establishments","nhgisCode":"AKV","sequence":37,"datasetName":"1870_cPAX","nVariables":1},{"name":"NT36B","description":"Wages + Paid in Manufacturing","universe":"Manufacturing Establishments","nhgisCode":"AKW","sequence":38,"datasetName":"1870_cPAX","nVariables":1},{"name":"NT36C","description":"Value + of Materials Used in Manufacturing","universe":"Manufacturing Establishments","nhgisCode":"AKX","sequence":39,"datasetName":"1870_cPAX","nVariables":1},{"name":"NT36D","description":"Value + of Products in Manufacturing","universe":"Manufacturing Establishments","nhgisCode":"AKY","sequence":40,"datasetName":"1870_cPAX","nVariables":1}],"pageNumber":81,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=80\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=82\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1238' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:21 GMT + Etag: + - W/"91252e7c0ec7bf89e3df30671bf7e8bf" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - 8ee71514-8be9-4b57-9ae0-ba3f2df840f0 + X-Runtime: + - '0.007449' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=82&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT37A","description":"Assessed Valuation of Real + and Personal Estate","universe":"Real Estate and Personal Property","nhgisCode":"AKZ","sequence":41,"datasetName":"1870_cPAX","nVariables":1},{"name":"NT37B","description":"True + Valuation of Real and Personal Estate","universe":"Real Estate and Personal + Property","nhgisCode":"AK0","sequence":42,"datasetName":"1870_cPAX","nVariables":1},{"name":"NT38","description":"Non-National + Taxation","universe":"Specified Geographic Area","nhgisCode":"AK1","sequence":43,"datasetName":"1870_cPAX","nVariables":1},{"name":"NT39","description":"Non-National + Taxation Type","universe":"Specified Geographic Area","nhgisCode":"AK2","sequence":44,"datasetName":"1870_cPAX","nVariables":3},{"name":"NT40","description":"Public + Debt by Debtor","universe":"Specified Geographic Area","nhgisCode":"AK4","sequence":45,"datasetName":"1870_cPAX","nVariables":2}],"pageNumber":82,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=81\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=83\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1217' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:21 GMT + Etag: + - W/"6ca1f54211088288b6c710729fb187a0" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - 007007ec-2056-4907-99a1-c2d921f0882c + X-Runtime: + - '0.006902' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=83&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT41","description":"Religious Organizations by Denomination","universe":"Religious + Organizations","nhgisCode":"AK5","sequence":46,"datasetName":"1870_cPAX","nVariables":19},{"name":"NT42","description":"Religious + Sittings by Denomination","universe":"Religious Sittings","nhgisCode":"AK6","sequence":47,"datasetName":"1870_cPAX","nVariables":19},{"name":"NT43","description":"Total + Religious Organizations of All Denominations","universe":"Religious Organizations","nhgisCode":"AK7","sequence":48,"datasetName":"1870_cPAX","nVariables":1},{"name":"NT44","description":"Total + Religious Edifices of All Denominations","universe":"Religious Edifices","nhgisCode":"AK8","sequence":49,"datasetName":"1870_cPAX","nVariables":1},{"name":"NT45","description":"Total + Religious Sittings of All Denominations","universe":"Religious Sittings","nhgisCode":"AK9","sequence":50,"datasetName":"1870_cPAX","nVariables":1}],"pageNumber":83,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=82\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=84\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1233' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:21 GMT + Etag: + - W/"c105f909ebbe5b789addc28baaa259d3" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - 060e3916-d41e-451b-b0ee-8b33a9777006 + X-Runtime: + - '0.009421' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=84&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT46","description":"Value of Property of all Denominations","universe":"Church + Property","nhgisCode":"ALA","sequence":51,"datasetName":"1870_cPAX","nVariables":1},{"name":"NT1","description":"Total + Population","universe":"Persons","nhgisCode":"ALZ","sequence":1,"datasetName":"1870_sPHX","nVariables":1},{"name":"NT2","description":"Total + Schools","universe":"Schools","nhgisCode":"AL4","sequence":2,"datasetName":"1870_sPHX","nVariables":1},{"name":"NT3","description":"Persons + at Schools","universe":"Teachers and Pupils at Schools","nhgisCode":"AMC","sequence":3,"datasetName":"1870_sPHX","nVariables":2},{"name":"NT4","description":"Persons + at Schools by Sex","universe":"Teachers and Pupils at Schools","nhgisCode":"AML","sequence":4,"datasetName":"1870_sPHX","nVariables":4}],"pageNumber":84,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=83\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=85\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1109' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:21 GMT + Etag: + - W/"364ec028ffd74de10eb40e0d53973177" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - 10dd88e6-9c75-4f82-9493-e1cb047affda + X-Runtime: + - '0.010847' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=85&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT7","description":"Total School Income, Year Ended + June 1","universe":"School Income","nhgisCode":"ANG","sequence":5,"datasetName":"1870_sPHX","nVariables":1},{"name":"NT8","description":"School + Income Type, Year Ended June 1","universe":"School Income","nhgisCode":"ANR","sequence":6,"datasetName":"1870_sPHX","nVariables":3},{"name":"NT9","description":"Type + of School","universe":"Schools","nhgisCode":"ANT","sequence":7,"datasetName":"1870_sPHX","nVariables":3},{"name":"NT10","description":"Type + of School by Persons at Schools","universe":"Teachers and Pupils at Schools","nhgisCode":"AL0","sequence":8,"datasetName":"1870_sPHX","nVariables":6},{"name":"NT11","description":"Type + of School by Persons at Schools by Sex","universe":"Teachers and Pupils at + Schools","nhgisCode":"AL1","sequence":9,"datasetName":"1870_sPHX","nVariables":12}],"pageNumber":85,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=84\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=86\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1172' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:22 GMT + Etag: + - W/"9ab0422384232da5e679597838988242" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - 7c1aaa37-1227-491e-a549-2b39c4912f27 + X-Runtime: + - '0.008804' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=86&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT14","description":"Total School Income, Year Ended + June 1 by Type of School","universe":"School Income","nhgisCode":"AL2","sequence":10,"datasetName":"1870_sPHX","nVariables":3},{"name":"NT15","description":"Type + of School by School Income Type, Year Ended June 1","universe":"Schools","nhgisCode":"AL3","sequence":11,"datasetName":"1870_sPHX","nVariables":9},{"name":"NT23","description":"Population + Attending School by Race by Sex","universe":"Persons Attending School","nhgisCode":"AL5","sequence":12,"datasetName":"1870_sPHX","nVariables":4},{"name":"NT24","description":"Population + 10 Years of Age and Over Who Cannot Write by Race by Age by Sex","universe":"Persons + 10 Years and Over Who Cannot Write","nhgisCode":"AL6","sequence":13,"datasetName":"1870_sPHX","nVariables":12},{"name":"NT25","description":"Total + Libraries","universe":"Libraries","nhgisCode":"AL7","sequence":14,"datasetName":"1870_sPHX","nVariables":1}],"pageNumber":86,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=85\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=87\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1256' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:22 GMT + Etag: + - W/"d337048a9097fb39eaf565b8b7f7ea82" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - d520fc34-e666-4c9c-8637-56baa4a194a5 + X-Runtime: + - '0.009465' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=87&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT26","description":"Total Volumes in Libraries","universe":"Libraries","nhgisCode":"AL8","sequence":15,"datasetName":"1870_sPHX","nVariables":1},{"name":"NT27","description":"Type + of Library","universe":"Libraries","nhgisCode":"AL9","sequence":16,"datasetName":"1870_sPHX","nVariables":2},{"name":"NT28","description":"Total + Volumes by Type of Library","universe":"Libraries","nhgisCode":"AMA","sequence":17,"datasetName":"1870_sPHX","nVariables":2},{"name":"NT29","description":"Non-Private + Library Type","universe":"Non-Private Libraries","nhgisCode":"AMB","sequence":18,"datasetName":"1870_sPHX","nVariables":10},{"name":"NT30","description":"Total + Volumes by Non-Private Library Type","universe":"Non-Private Libraries","nhgisCode":"AMD","sequence":19,"datasetName":"1870_sPHX","nVariables":10}],"pageNumber":87,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=86\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=88\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1127' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:22 GMT + Etag: + - W/"5ca4ea35c755f7906897d26913725a25" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - 6fc8b567-5495-4c9f-ac19-54bfade0f5fd + X-Runtime: + - '0.006888' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=88&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT33","description":"Area","universe":"Specified + Geographic Area","nhgisCode":"AME","sequence":20,"datasetName":"1870_sPHX","nVariables":1},{"name":"NT34","description":"Population + per Square Mile","universe":"Persons","nhgisCode":"AMF","sequence":21,"datasetName":"1870_sPHX","nVariables":1},{"name":"NT35","description":"Total + Families","universe":"Families","nhgisCode":"AMG","sequence":22,"datasetName":"1870_sPHX","nVariables":1},{"name":"NT36","description":"Average + Family Size","universe":"Families","nhgisCode":"AMH","sequence":23,"datasetName":"1870_sPHX","nVariables":1},{"name":"NT37","description":"Dwellings","universe":"Dwellings","nhgisCode":"AMI","sequence":24,"datasetName":"1870_sPHX","nVariables":1}],"pageNumber":88,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=87\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=89\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1047' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:22 GMT + Etag: + - W/"95ac312797fa0b8929a728dcb5d9a364" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - 4f4d3baa-dd45-4940-9402-3ad675137e0d + X-Runtime: + - '0.010514' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=89&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT38","description":"Average Persons per Dwelling","universe":"Dwellings","nhgisCode":"AMJ","sequence":25,"datasetName":"1870_sPHX","nVariables":1},{"name":"NT39","description":"Total + Population 5 to 18 Years of Age by Sex","universe":"Persons 5 to 18 Years + of Age","nhgisCode":"AMK","sequence":26,"datasetName":"1870_sPHX","nVariables":2},{"name":"NT40","description":"Population + 5 to 18 Years of Age by Nativity by Sex","universe":"Persons 5 to 18 Years + of Age","nhgisCode":"AMM","sequence":27,"datasetName":"1870_sPHX","nVariables":4},{"name":"NT41","description":"Population + 5 to 18 Years of Age by Race by Sex","universe":"Persons 5 to 18 Years of + Age","nhgisCode":"AMN","sequence":28,"datasetName":"1870_sPHX","nVariables":8},{"name":"NT42","description":"Total + Male Population 18 to 45 Years of Age","universe":"Males 18 to 45 Years of + Age","nhgisCode":"AMO","sequence":29,"datasetName":"1870_sPHX","nVariables":1}],"pageNumber":89,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=88\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=90\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1249' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:22 GMT + Etag: + - W/"239fbaa13b52d01f1b31109fabe571d6" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - 45c452da-8d6f-4883-9bf2-aaddf86b0da1 + X-Runtime: + - '0.007886' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=90&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT43","description":"Male Population 18 to 45 Years + of Age by Nativity","universe":"Males 18 to 45 Years of Age","nhgisCode":"AMP","sequence":30,"datasetName":"1870_sPHX","nVariables":2},{"name":"NT44","description":"Male + Population 18 to 45 Years of Age by Race","universe":"Males 18 to 45 Years + of Age","nhgisCode":"AMQ","sequence":31,"datasetName":"1870_sPHX","nVariables":4},{"name":"NT45","description":"Total + Male Population 21 Years of Age and Over","universe":"Males 21 Years and Over","nhgisCode":"AMR","sequence":32,"datasetName":"1870_sPHX","nVariables":1},{"name":"NT46","description":"Male + Population 21 Years of Age and Over by Nativity","universe":"Males 21 Years + and Over","nhgisCode":"AMS","sequence":33,"datasetName":"1870_sPHX","nVariables":2},{"name":"NT47","description":"Male + Population 21 Years of Age and Over by Race","universe":"Males 21 Years and + Over","nhgisCode":"AMT","sequence":34,"datasetName":"1870_sPHX","nVariables":4}],"pageNumber":90,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=89\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=91\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1281' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:22 GMT + Etag: + - W/"db03bdc75327402a8e04c5e6e833a972" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - 6ee93d68-f0c5-41af-93e8-cf6c2f77b30d + X-Runtime: + - '0.009817' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=91&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT48","description":"Male Citizens 21 Years of Age + and Over","universe":"Male Citizens 21 Years and Over","nhgisCode":"AMU","sequence":35,"datasetName":"1870_sPHX","nVariables":1},{"name":"NT49","description":"Deaths + by Sex","universe":"Deaths","nhgisCode":"AMV","sequence":36,"datasetName":"1870_sPHX","nVariables":2},{"name":"NT50","description":"Assessed + Valuation of Property","universe":"Property","nhgisCode":"AMW","sequence":37,"datasetName":"1870_sPHX","nVariables":1},{"name":"NT51","description":"Assessed + Valuation by Property Type","universe":"Estate Property","nhgisCode":"AMX","sequence":38,"datasetName":"1870_sPHX","nVariables":2},{"name":"NT52","description":"True + Value of Property","universe":"Property","nhgisCode":"AMY","sequence":39,"datasetName":"1870_sPHX","nVariables":1}],"pageNumber":91,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=90\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=92\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1124' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:22 GMT + Etag: + - W/"61979d2e51e05ed6866dcd2b5c783121" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - a89cec44-85b6-41c9-8ff3-b8726c9eee1f + X-Runtime: + - '0.009859' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=92&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT53","description":"Total Newspapers and Periodicals","universe":"Newspapers + and Periodicals","nhgisCode":"AMZ","sequence":40,"datasetName":"1870_sPHX","nVariables":1},{"name":"NT54","description":"Newspaper + and Periodical Copies Annually Issued","universe":"Newspaper and Periodical + Copies Annually Issued","nhgisCode":"AM0","sequence":41,"datasetName":"1870_sPHX","nVariables":1},{"name":"NT55","description":"Circulation + of Newspapers and Periodicals","universe":"Newspapers and Periodicals","nhgisCode":"AM1","sequence":42,"datasetName":"1870_sPHX","nVariables":1},{"name":"NT56","description":"Newspapers + and Periodicals by Frequency of Issue","universe":"Newspapers and Periodicals","nhgisCode":"AM2","sequence":43,"datasetName":"1870_sPHX","nVariables":9},{"name":"NT57","description":"Circulation + of Newspapers and Periodicals by Frequency of Issue","universe":"Newspapers + and Periodicals","nhgisCode":"AM3","sequence":44,"datasetName":"1870_sPHX","nVariables":9}],"pageNumber":92,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=91\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=93\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1300' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:22 GMT + Etag: + - W/"2a3e9c05b89bad9d9291f0f78dee848e" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - ee0a9bdf-7728-4b25-8739-0beebaf9dccf + X-Runtime: + - '0.008850' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=93&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT58","description":"Total Newspapers and Periodicals + by Newspaper and Periodical Type","universe":"Newspapers and Periodicals","nhgisCode":"AM4","sequence":45,"datasetName":"1870_sPHX","nVariables":10},{"name":"NT59","description":"Total + Newspaper and Periodical Copies Annually Issued by Newspaper and Periodical + Type","universe":"Newspaper and Periodical Copies Annually Issued","nhgisCode":"AM5","sequence":46,"datasetName":"1870_sPHX","nVariables":10},{"name":"NT60","description":"Total + Circulation by Newspaper and Periodical Type","universe":"Newspapers and Periodicals","nhgisCode":"AM6","sequence":47,"datasetName":"1870_sPHX","nVariables":10},{"name":"NT61","description":"Advertising + Newspapers and Periodicals by Frequency of Issue","universe":"Advertising + Newspapers and Periodicals","nhgisCode":"AM7","sequence":48,"datasetName":"1870_sPHX","nVariables":5},{"name":"NT62","description":"Circulation + of Advertising Newspapers and Periodicals by Frequency of Issue","universe":"Advertising + Newspapers and Periodicals","nhgisCode":"AM8","sequence":49,"datasetName":"1870_sPHX","nVariables":5}],"pageNumber":93,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=92\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=94\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1432' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:22 GMT + Etag: + - W/"ee7027a86a8df947532d668e9a542573" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - ea7b8203-a641-4fd5-940c-70c8bef763ac + X-Runtime: + - '0.008058' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=94&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT63","description":"Agricultural and Horticultural + Newspapers and Periodicals by Frequency of Issue","universe":"Agricultural + and Horticultural Newspapers and Periodicals","nhgisCode":"AM9","sequence":50,"datasetName":"1870_sPHX","nVariables":3},{"name":"NT64","description":"Circulation + of Agricultural and Horticultural Newspapers and Periodicals by Frequency + of Issue","universe":"Agricultural and Horticultural Newspapers and Periodicals","nhgisCode":"ANA","sequence":51,"datasetName":"1870_sPHX","nVariables":3},{"name":"NT65","description":"Newspapers + and Periodicals of Benevolent and Secret Societies by Frequency of Issue","universe":"Newspapers + and Periodicals of Benevolent and Secret Societies","nhgisCode":"ANB","sequence":52,"datasetName":"1870_sPHX","nVariables":4},{"name":"NT66","description":"Circulation + of Newspapers and Periodicals of Benevolent and Secret Societies by Frequency + of Issue","universe":"Newspapers and Periodicals of Benevolent and Secret + Societies","nhgisCode":"ANC","sequence":53,"datasetName":"1870_sPHX","nVariables":4},{"name":"NT67","description":"Commercial + and Financial Newspapers and Periodicals by Frequency of Issue","universe":"Commercial + and Financial Newspapers and Periodicals","nhgisCode":"AND","sequence":54,"datasetName":"1870_sPHX","nVariables":8}],"pageNumber":94,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=93\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=95\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1632' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:22 GMT + Etag: + - W/"50980f6ec47a8dfa586c59a5f70c111c" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - 7c1aa83e-1d76-49f1-b09a-7f65f0ab82dd + X-Runtime: + - '0.008587' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=95&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT68","description":"Circulation of Commercial and + Financial Newspapers and Periodicals by Frequency of Issue","universe":"Commercial + and Financial Newspapers and Periodicals","nhgisCode":"ANE","sequence":55,"datasetName":"1870_sPHX","nVariables":8},{"name":"NT69","description":"Illustrated, + Literary, and Miscellaneous Newspapers and Periodicals by Frequency of Issue","universe":"Illustrated, + Literary, and Miscellaneous Newspapers and Periodicals","nhgisCode":"ANF","sequence":56,"datasetName":"1870_sPHX","nVariables":8},{"name":"NT70","description":"Circulation + of Illustrated, Literary, and Miscellaneous Newspapers and Periodicals by + Frequency of Issue","universe":"Illustrated, Literary, and Miscellaneous Newspapers + and Periodicals","nhgisCode":"ANH","sequence":57,"datasetName":"1870_sPHX","nVariables":8},{"name":"NT71","description":"Newspapers + and Periodicals Devoted to Nationality by Frequency of Issue","universe":"Newspapers + and Periodicals Devoted to Nationality","nhgisCode":"ANI","sequence":58,"datasetName":"1870_sPHX","nVariables":5},{"name":"NT72","description":"Circulation + of Newspapers and Periodicals Devoted to Nationality by Frequency of Issue","universe":"Newspapers + and Periodicals Devoted to Nationality","nhgisCode":"ANJ","sequence":59,"datasetName":"1870_sPHX","nVariables":5}],"pageNumber":95,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=94\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=96\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1639' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:22 GMT + Etag: + - W/"a51bc51170673b09f74beb0a5ed32e81" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - 6c22a7ac-8949-4b49-a9b6-7e99f37ddb5c + X-Runtime: + - '0.007530' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=96&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT73","description":"Political Newspapers and Periodicals + by Frequency of Issue","universe":"Political Newspapers and Periodicals","nhgisCode":"ANK","sequence":60,"datasetName":"1870_sPHX","nVariables":6},{"name":"NT74","description":"Circulation + of Political Newspapers and Periodicals by Frequency of Issue","universe":"Political + Newspapers and Periodicals","nhgisCode":"ANL","sequence":61,"datasetName":"1870_sPHX","nVariables":6},{"name":"NT75","description":"Religious + Newspapers and Periodicals by Frequency of Issue","universe":"Religious Newspapers + and Periodicals","nhgisCode":"ANM","sequence":62,"datasetName":"1870_sPHX","nVariables":5},{"name":"NT76","description":"Circulation + of Religious Newspapers and Periodicals by Frequency of Issue","universe":"Religious + Newspapers and Periodicals","nhgisCode":"ANN","sequence":63,"datasetName":"1870_sPHX","nVariables":5},{"name":"NT77","description":"Sporting + Newspapers and Periodicals by Frequency of Issue","universe":"Sporting Newspapers + and Periodicals","nhgisCode":"ANO","sequence":64,"datasetName":"1870_sPHX","nVariables":2}],"pageNumber":96,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=95\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=97\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1416' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:22 GMT + Etag: + - W/"6756cb1b86f407de5a9669ed7dbc6150" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - 1205e811-4e19-4a8b-b443-8e856324dca3 + X-Runtime: + - '0.009171' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=97&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT78","description":"Circulation of Sporting Newspapers + and Periodicals by Frequency of Issue","universe":"Sporting Newspapers and + Periodicals","nhgisCode":"ANP","sequence":65,"datasetName":"1870_sPHX","nVariables":2},{"name":"NT79","description":"Technical + and Professional Newspapers and Periodicals by Frequency of Issue","universe":"Technical + and Professional Newspapers and Periodicals","nhgisCode":"ANQ","sequence":66,"datasetName":"1870_sPHX","nVariables":6},{"name":"NT80","description":"Circulation + of Technical and Professional Newspapers and Periodicals by Frequency of Issue","universe":"Technical + and Professional Newspapers and Periodicals","nhgisCode":"ANS","sequence":67,"datasetName":"1870_sPHX","nVariables":6},{"name":"NT1","description":"Nativity","universe":"Persons","nhgisCode":"AN5","sequence":1,"datasetName":"1870_sRN","nVariables":2},{"name":"NT2","description":"Place + of Birth","universe":"Persons","nhgisCode":"AN6","sequence":2,"datasetName":"1870_sRN","nVariables":91}],"pageNumber":97,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=96\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=98\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1327' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:23 GMT + Etag: + - W/"ab225954ab17f067659e08141224b93f" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - 2bf30027-1fa2-4b68-92b8-f5128851bf7a + X-Runtime: + - '0.008519' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=98&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT3","description":"Population Born in British America, + Germany and Great Britain by Place of Birth","universe":"Persons Born in British + America, Germany and Great Britain","nhgisCode":"AN7","sequence":3,"datasetName":"1870_sRN","nVariables":26},{"name":"NT4","description":"Race + by Nativity","universe":"Persons","nhgisCode":"AN8","sequence":4,"datasetName":"1870_sRN","nVariables":8},{"name":"NT5","description":"Race + by Place of Birth","universe":"Persons","nhgisCode":"AN9","sequence":5,"datasetName":"1870_sRN","nVariables":364},{"name":"NT6","description":"Population + Born in British America, Germany and Great Britain by Race by Place of Birth","universe":"Persons + Born in British America, Germany and Great Britain","nhgisCode":"AOA","sequence":6,"datasetName":"1870_sRN","nVariables":104},{"name":"NT1","description":"Religious + Organizations of All Denominations","universe":"Religious Organizations","nhgisCode":"ALH","sequence":1,"datasetName":"1870_sROG","nVariables":1}],"pageNumber":98,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=97\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=99\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1310' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:23 GMT + Etag: + - W/"83919ade950df0733a603add33b5087c" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - a4cec897-764c-47f6-aabe-ab71c30bbe25 + X-Runtime: + - '0.008794' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=99&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT2","description":"Religious Edifices of All Denominations","universe":"Religious + Edifices","nhgisCode":"ALR","sequence":2,"datasetName":"1870_sROG","nVariables":1},{"name":"NT3","description":"Religious + Sittings of All Denominations","universe":"Religious Sittings","nhgisCode":"ALS","sequence":3,"datasetName":"1870_sROG","nVariables":1},{"name":"NT4","description":"Value + of Religious Property of All Denominations","universe":"Religious Property","nhgisCode":"ALT","sequence":4,"datasetName":"1870_sROG","nVariables":1},{"name":"NT5","description":"Religious + Organizations by Denomination","universe":"Religious Organizations","nhgisCode":"ALU","sequence":5,"datasetName":"1870_sROG","nVariables":27},{"name":"NT6","description":"Religious + Edifices by Denomination","universe":"Religious Edifices","nhgisCode":"ALV","sequence":6,"datasetName":"1870_sROG","nVariables":27}],"pageNumber":99,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=98\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=100\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1205' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:23 GMT + Etag: + - W/"07449b1a7258901ff3a3bb2607df7125" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - 71fdf8c9-5232-46ab-b9dc-6bf844d9bd42 + X-Runtime: + - '0.008610' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=100&pageSize=5&version=2 + response: + body: + string: '{"data":[{"name":"NT7","description":"Religious Sittings by Denomination","universe":"Religious + Sittings","nhgisCode":"ALW","sequence":7,"datasetName":"1870_sROG","nVariables":27},{"name":"NT8","description":"Value + of Religious Property by Denomination","universe":"Religious Property","nhgisCode":"ALX","sequence":8,"datasetName":"1870_sROG","nVariables":27},{"name":"NT9","description":"Total + Number of People Employed in All Classes of Occupations","universe":"Employed + Persons","nhgisCode":"ALY","sequence":9,"datasetName":"1870_sROG","nVariables":1},{"name":"NT10","description":"People + Employed in All Classes of Occupations by Age by Sex","universe":"Employed + Persons","nhgisCode":"ALI","sequence":10,"datasetName":"1870_sROG","nVariables":6},{"name":"NT11","description":"People + Employed in All Classes of Occupations by Place of Birth","universe":"Employed + Persons","nhgisCode":"ALJ","sequence":11,"datasetName":"1870_sROG","nVariables":13}],"pageNumber":100,"pageSize":5,"totalCount":51509,"links":{"previousPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=99\u0026pageSize=5\u0026version=2","nextPage":"https://api.ipums.org/metadata/data_tables?collection=nhgis\u0026pageNumber=101\u0026pageSize=5\u0026version=2"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '1261' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 10 Jan 2025 20:50:23 GMT + Etag: + - W/"16cd057494becbe591f8b7f14743070e" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.22.1 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - '-1' + X-Ratelimit-Remaining: + - '0' + X-Ratelimit-Reset: + - '0' + X-Request-Id: + - f9ad2227-0575-477a-94db-dc37a2c08025 + X-Runtime: + - '0.009862' + X-Xss-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-ipumspy:0.5.1.github.com/ipums/ipumspy + method: GET + uri: https://api.ipums.org/metadata/data_tables?collection=nhgis&pageNumber=101&pageSize=5&version=2 + response: + body: + string: "{\n \"error\": \"Rate limit exceeded\"\n}" + headers: + Content-Length: + - '38' + Content-Type: + - application/json + Date: + - Fri, 10 Jan 2025 20:50:23 GMT + Vary: + - Origin + X-Generator: + - tyk.io + status: + code: 429 + message: Too Many Requests +version: 1 diff --git a/tests/test_metadata.py b/tests/test_metadata.py index 96b4db0..9803d75 100644 --- a/tests/test_metadata.py +++ b/tests/test_metadata.py @@ -9,6 +9,7 @@ TimeSeriesTableMetadata, ) +from ipumspy.api.exceptions import IpumsApiRateLimitException @pytest.fixture(scope="function") def live_api_client(environment_variables) -> IpumsApiClient: @@ -68,3 +69,13 @@ def test_collection_validity(): with pytest.raises(ValueError) as exc_info: ds = DatasetMetadata("usa", "1990_STF1") assert exc_info.value.args[0] == "DatasetMetadata is not a valid metadata type for the usa collection." + + +@pytest.mark.vcr +@pytest.mark.slow +def test_ipums_api_rate_limit_exception(live_api_client: IpumsApiClient): + with pytest.raises(IpumsApiRateLimitException) as exc_info: + for page in live_api_client.get_metadata_catalog("nhgis", metadata_type="data_tables", page_size=5): + for dt in page["data"]: + continue + assert exc_info.value.args[0] == "You have exceeded the API rate limit." From 87dffdbacb207bae08cfb0227131820e13dff80a Mon Sep 17 00:00:00 2001 From: renae-r Date: Fri, 10 Jan 2025 15:00:33 -0600 Subject: [PATCH 31/35] :black_heart: --- src/ipumspy/api/core.py | 4 +++- src/ipumspy/api/exceptions.py | 3 ++- src/ipumspy/api/metadata.py | 25 ++++++++++++------------- tests/test_metadata.py | 12 +++++++++--- 4 files changed, 26 insertions(+), 18 deletions(-) diff --git a/src/ipumspy/api/core.py b/src/ipumspy/api/core.py index 9a9dc9e..b6ca30f 100644 --- a/src/ipumspy/api/core.py +++ b/src/ipumspy/api/core.py @@ -144,7 +144,9 @@ def request(self, method: str, *args, **kwargs) -> requests.Response: "Page not found. Perhaps you passed the wrong extract id or an invalid page size?" ) elif response.status_code == HTTPStatus.TOO_MANY_REQUESTS: - raise IpumsApiRateLimitException("You have exceeded the API rate limit.") + raise IpumsApiRateLimitException( + "You have exceeded the API rate limit." + ) else: error_details = _prettify_message(response.json()["detail"]) raise IpumsApiException(error_details) diff --git a/src/ipumspy/api/exceptions.py b/src/ipumspy/api/exceptions.py index 0f8565c..1eb401f 100644 --- a/src/ipumspy/api/exceptions.py +++ b/src/ipumspy/api/exceptions.py @@ -40,5 +40,6 @@ class BadIpumsApiRequest(IpumsApiException): class IpumsExtractNotSubmitted(IpumsApiException): """Represents the case when an extract needs to be submitted before the operation can be performed""" + class IpumsApiRateLimitException(IpumsApiException): - """Represents a request that exceeds the IPUMS API rate limit""" \ No newline at end of file + """Represents a request that exceeds the IPUMS API rate limit""" diff --git a/src/ipumspy/api/metadata.py b/src/ipumspy/api/metadata.py index c4c4b53..631d8b7 100644 --- a/src/ipumspy/api/metadata.py +++ b/src/ipumspy/api/metadata.py @@ -13,7 +13,7 @@ class IpumsMetadata(ABC): Class to request and store metadata for an arbitrary IPUMS resource. Use a subclass to request metadata for a particular type of resource. """ - + def populate(self, metadata_response_dict: dict): """ Update IpumsMetadata objects with attributes from API response. @@ -26,7 +26,7 @@ def populate(self, metadata_response_dict: dict): setattr(self, attribute, metadata_response_dict[attribute]) else: raise KeyError(f"{type(self).__name__} has no attribute '{attribute}'.") - + @property @abstractmethod def supported_collections(self): @@ -34,12 +34,12 @@ def supported_collections(self): Collections that support this metadata class """ pass - + def _validate_collection(self): if self.collection not in self.supported_collections: - raise ValueError(f"{type(self).__name__} is not a valid metadata type for the {self.collection} collection.") - - + raise ValueError( + f"{type(self).__name__} is not a valid metadata type for the {self.collection} collection." + ) @dataclass @@ -93,13 +93,12 @@ class DatasetMetadata(IpumsMetadata): def __post_init__(self): self._path = f"metadata/datasets/{self.name}" self._validate_collection() - + @property def supported_collections(self): return ["nhgis"] - @dataclass class TimeSeriesTableMetadata(IpumsMetadata): """ @@ -134,12 +133,10 @@ class TimeSeriesTableMetadata(IpumsMetadata): def __post_init__(self): self._path = f"metadata/time_series_tables/{self.name}" self._validate_collection() - + @property def supported_collections(self): return ["nhgis"] - - @dataclass @@ -174,9 +171,11 @@ class DataTableMetadata(IpumsMetadata): """Dictionary containing variable descriptions and codes for the variables included in the data table""" def __post_init__(self): - self._path = self._path = f"metadata/datasets/{self.dataset_name}/data_tables/{self.name}" + self._path = self._path = ( + f"metadata/datasets/{self.dataset_name}/data_tables/{self.name}" + ) self._validate_collection() - + @property def supported_collections(self): return ["nhgis"] diff --git a/tests/test_metadata.py b/tests/test_metadata.py index 9803d75..d4bd36e 100644 --- a/tests/test_metadata.py +++ b/tests/test_metadata.py @@ -11,6 +11,7 @@ from ipumspy.api.exceptions import IpumsApiRateLimitException + @pytest.fixture(scope="function") def live_api_client(environment_variables) -> IpumsApiClient: live_client = IpumsApiClient(os.environ.get("IPUMS_API_KEY")) @@ -63,19 +64,24 @@ def test_get_metadata(live_api_client: IpumsApiClient): assert len(ds.data_tables) == 100 assert len(dt.variables) == 49 - + def test_collection_validity(): with pytest.raises(ValueError) as exc_info: ds = DatasetMetadata("usa", "1990_STF1") - assert exc_info.value.args[0] == "DatasetMetadata is not a valid metadata type for the usa collection." + assert ( + exc_info.value.args[0] + == "DatasetMetadata is not a valid metadata type for the usa collection." + ) @pytest.mark.vcr @pytest.mark.slow def test_ipums_api_rate_limit_exception(live_api_client: IpumsApiClient): with pytest.raises(IpumsApiRateLimitException) as exc_info: - for page in live_api_client.get_metadata_catalog("nhgis", metadata_type="data_tables", page_size=5): + for page in live_api_client.get_metadata_catalog( + "nhgis", metadata_type="data_tables", page_size=5 + ): for dt in page["data"]: continue assert exc_info.value.args[0] == "You have exceeded the API rate limit." From 84e154934334903a50ea6100f3a946bf95ab1540 Mon Sep 17 00:00:00 2001 From: renae-r Date: Fri, 10 Jan 2025 17:41:23 -0600 Subject: [PATCH 32/35] update dependencies and supported versions of Python --- .github/workflows/main.yml | 2 +- docs/source/getting_started.rst | 2 +- poetry.lock | 311 +++++++++++++++++++------------- pyproject.toml | 12 +- 4 files changed, 197 insertions(+), 130 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0a30baa..a2b0876 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -13,7 +13,7 @@ jobs: strategy: fail-fast: true matrix: - python-version: ["3.8", "3.9", "3.10", "3.11"] + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] steps: - uses: actions/checkout@v4 diff --git a/docs/source/getting_started.rst b/docs/source/getting_started.rst index b34793a..a315a2d 100644 --- a/docs/source/getting_started.rst +++ b/docs/source/getting_started.rst @@ -8,7 +8,7 @@ Getting Started Installation ------------ -This package requires that you have at least Python 3.8 installed. +This package requires that you have at least Python 3.9 installed. Install with ``pip``: diff --git a/poetry.lock b/poetry.lock index 21957e6..527d8d0 100644 --- a/poetry.lock +++ b/poetry.lock @@ -24,9 +24,6 @@ files = [ {file = "annotated_types-0.6.0.tar.gz", hash = "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d"}, ] -[package.dependencies] -typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.9\""} - [[package]] name = "anyio" version = "4.3.0" @@ -102,9 +99,6 @@ files = [ {file = "babel-2.15.0.tar.gz", hash = "sha256:8daf0e265d05768bc6c7a314cf1321e9a123afc328cc635c18622a2f30a04413"}, ] -[package.dependencies] -pytz = {version = ">=2015.7", markers = "python_version < \"3.9\""} - [package.extras] dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] @@ -1002,40 +996,57 @@ testing-docutils = ["pygments", "pytest (>=7,<8)", "pytest-param-files (>=0.3.4, [[package]] name = "numpy" -version = "1.24.4" +version = "2.0.2" description = "Fundamental package for array computing in Python" category = "main" optional = false -python-versions = ">=3.8" -files = [ - {file = "numpy-1.24.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c0bfb52d2169d58c1cdb8cc1f16989101639b34c7d3ce60ed70b19c63eba0b64"}, - {file = "numpy-1.24.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ed094d4f0c177b1b8e7aa9cba7d6ceed51c0e569a5318ac0ca9a090680a6a1b1"}, - {file = "numpy-1.24.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79fc682a374c4a8ed08b331bef9c5f582585d1048fa6d80bc6c35bc384eee9b4"}, - {file = "numpy-1.24.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ffe43c74893dbf38c2b0a1f5428760a1a9c98285553c89e12d70a96a7f3a4d6"}, - {file = "numpy-1.24.4-cp310-cp310-win32.whl", hash = "sha256:4c21decb6ea94057331e111a5bed9a79d335658c27ce2adb580fb4d54f2ad9bc"}, - {file = "numpy-1.24.4-cp310-cp310-win_amd64.whl", hash = "sha256:b4bea75e47d9586d31e892a7401f76e909712a0fd510f58f5337bea9572c571e"}, - {file = "numpy-1.24.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f136bab9c2cfd8da131132c2cf6cc27331dd6fae65f95f69dcd4ae3c3639c810"}, - {file = "numpy-1.24.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e2926dac25b313635e4d6cf4dc4e51c8c0ebfed60b801c799ffc4c32bf3d1254"}, - {file = "numpy-1.24.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:222e40d0e2548690405b0b3c7b21d1169117391c2e82c378467ef9ab4c8f0da7"}, - {file = "numpy-1.24.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7215847ce88a85ce39baf9e89070cb860c98fdddacbaa6c0da3ffb31b3350bd5"}, - {file = "numpy-1.24.4-cp311-cp311-win32.whl", hash = "sha256:4979217d7de511a8d57f4b4b5b2b965f707768440c17cb70fbf254c4b225238d"}, - {file = "numpy-1.24.4-cp311-cp311-win_amd64.whl", hash = "sha256:b7b1fc9864d7d39e28f41d089bfd6353cb5f27ecd9905348c24187a768c79694"}, - {file = "numpy-1.24.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1452241c290f3e2a312c137a9999cdbf63f78864d63c79039bda65ee86943f61"}, - {file = "numpy-1.24.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:04640dab83f7c6c85abf9cd729c5b65f1ebd0ccf9de90b270cd61935eef0197f"}, - {file = "numpy-1.24.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5425b114831d1e77e4b5d812b69d11d962e104095a5b9c3b641a218abcc050e"}, - {file = "numpy-1.24.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd80e219fd4c71fc3699fc1dadac5dcf4fd882bfc6f7ec53d30fa197b8ee22dc"}, - {file = "numpy-1.24.4-cp38-cp38-win32.whl", hash = "sha256:4602244f345453db537be5314d3983dbf5834a9701b7723ec28923e2889e0bb2"}, - {file = "numpy-1.24.4-cp38-cp38-win_amd64.whl", hash = "sha256:692f2e0f55794943c5bfff12b3f56f99af76f902fc47487bdfe97856de51a706"}, - {file = "numpy-1.24.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2541312fbf09977f3b3ad449c4e5f4bb55d0dbf79226d7724211acc905049400"}, - {file = "numpy-1.24.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9667575fb6d13c95f1b36aca12c5ee3356bf001b714fc354eb5465ce1609e62f"}, - {file = "numpy-1.24.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3a86ed21e4f87050382c7bc96571755193c4c1392490744ac73d660e8f564a9"}, - {file = "numpy-1.24.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d11efb4dbecbdf22508d55e48d9c8384db795e1b7b51ea735289ff96613ff74d"}, - {file = "numpy-1.24.4-cp39-cp39-win32.whl", hash = "sha256:6620c0acd41dbcb368610bb2f4d83145674040025e5536954782467100aa8835"}, - {file = "numpy-1.24.4-cp39-cp39-win_amd64.whl", hash = "sha256:befe2bf740fd8373cf56149a5c23a0f601e82869598d41f8e188a0e9869926f8"}, - {file = "numpy-1.24.4-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:31f13e25b4e304632a4619d0e0777662c2ffea99fcae2029556b17d8ff958aef"}, - {file = "numpy-1.24.4-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95f7ac6540e95bc440ad77f56e520da5bf877f87dca58bd095288dce8940532a"}, - {file = "numpy-1.24.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e98f220aa76ca2a977fe435f5b04d7b3470c0a2e6312907b37ba6068f26787f2"}, - {file = "numpy-1.24.4.tar.gz", hash = "sha256:80f5e3a4e498641401868df4208b74581206afbee7cf7b8329daae82676d9463"}, +python-versions = ">=3.9" +files = [ + {file = "numpy-2.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:51129a29dbe56f9ca83438b706e2e69a39892b5eda6cedcb6b0c9fdc9b0d3ece"}, + {file = "numpy-2.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f15975dfec0cf2239224d80e32c3170b1d168335eaedee69da84fbe9f1f9cd04"}, + {file = "numpy-2.0.2-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:8c5713284ce4e282544c68d1c3b2c7161d38c256d2eefc93c1d683cf47683e66"}, + {file = "numpy-2.0.2-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:becfae3ddd30736fe1889a37f1f580e245ba79a5855bff5f2a29cb3ccc22dd7b"}, + {file = "numpy-2.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2da5960c3cf0df7eafefd806d4e612c5e19358de82cb3c343631188991566ccd"}, + {file = "numpy-2.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:496f71341824ed9f3d2fd36cf3ac57ae2e0165c143b55c3a035ee219413f3318"}, + {file = "numpy-2.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a61ec659f68ae254e4d237816e33171497e978140353c0c2038d46e63282d0c8"}, + {file = "numpy-2.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d731a1c6116ba289c1e9ee714b08a8ff882944d4ad631fd411106a30f083c326"}, + {file = "numpy-2.0.2-cp310-cp310-win32.whl", hash = "sha256:984d96121c9f9616cd33fbd0618b7f08e0cfc9600a7ee1d6fd9b239186d19d97"}, + {file = "numpy-2.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:c7b0be4ef08607dd04da4092faee0b86607f111d5ae68036f16cc787e250a131"}, + {file = "numpy-2.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:49ca4decb342d66018b01932139c0961a8f9ddc7589611158cb3c27cbcf76448"}, + {file = "numpy-2.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:11a76c372d1d37437857280aa142086476136a8c0f373b2e648ab2c8f18fb195"}, + {file = "numpy-2.0.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:807ec44583fd708a21d4a11d94aedf2f4f3c3719035c76a2bbe1fe8e217bdc57"}, + {file = "numpy-2.0.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8cafab480740e22f8d833acefed5cc87ce276f4ece12fdaa2e8903db2f82897a"}, + {file = "numpy-2.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a15f476a45e6e5a3a79d8a14e62161d27ad897381fecfa4a09ed5322f2085669"}, + {file = "numpy-2.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13e689d772146140a252c3a28501da66dfecd77490b498b168b501835041f951"}, + {file = "numpy-2.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9ea91dfb7c3d1c56a0e55657c0afb38cf1eeae4544c208dc465c3c9f3a7c09f9"}, + {file = "numpy-2.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c1c9307701fec8f3f7a1e6711f9089c06e6284b3afbbcd259f7791282d660a15"}, + {file = "numpy-2.0.2-cp311-cp311-win32.whl", hash = "sha256:a392a68bd329eafac5817e5aefeb39038c48b671afd242710b451e76090e81f4"}, + {file = "numpy-2.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:286cd40ce2b7d652a6f22efdfc6d1edf879440e53e76a75955bc0c826c7e64dc"}, + {file = "numpy-2.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:df55d490dea7934f330006d0f81e8551ba6010a5bf035a249ef61a94f21c500b"}, + {file = "numpy-2.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8df823f570d9adf0978347d1f926b2a867d5608f434a7cff7f7908c6570dcf5e"}, + {file = "numpy-2.0.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:9a92ae5c14811e390f3767053ff54eaee3bf84576d99a2456391401323f4ec2c"}, + {file = "numpy-2.0.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:a842d573724391493a97a62ebbb8e731f8a5dcc5d285dfc99141ca15a3302d0c"}, + {file = "numpy-2.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c05e238064fc0610c840d1cf6a13bf63d7e391717d247f1bf0318172e759e692"}, + {file = "numpy-2.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0123ffdaa88fa4ab64835dcbde75dcdf89c453c922f18dced6e27c90d1d0ec5a"}, + {file = "numpy-2.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:96a55f64139912d61de9137f11bf39a55ec8faec288c75a54f93dfd39f7eb40c"}, + {file = "numpy-2.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ec9852fb39354b5a45a80bdab5ac02dd02b15f44b3804e9f00c556bf24b4bded"}, + {file = "numpy-2.0.2-cp312-cp312-win32.whl", hash = "sha256:671bec6496f83202ed2d3c8fdc486a8fc86942f2e69ff0e986140339a63bcbe5"}, + {file = "numpy-2.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:cfd41e13fdc257aa5778496b8caa5e856dc4896d4ccf01841daee1d96465467a"}, + {file = "numpy-2.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9059e10581ce4093f735ed23f3b9d283b9d517ff46009ddd485f1747eb22653c"}, + {file = "numpy-2.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:423e89b23490805d2a5a96fe40ec507407b8ee786d66f7328be214f9679df6dd"}, + {file = "numpy-2.0.2-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:2b2955fa6f11907cf7a70dab0d0755159bca87755e831e47932367fc8f2f2d0b"}, + {file = "numpy-2.0.2-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:97032a27bd9d8988b9a97a8c4d2c9f2c15a81f61e2f21404d7e8ef00cb5be729"}, + {file = "numpy-2.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e795a8be3ddbac43274f18588329c72939870a16cae810c2b73461c40718ab1"}, + {file = "numpy-2.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26b258c385842546006213344c50655ff1555a9338e2e5e02a0756dc3e803dd"}, + {file = "numpy-2.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5fec9451a7789926bcf7c2b8d187292c9f93ea30284802a0ab3f5be8ab36865d"}, + {file = "numpy-2.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9189427407d88ff25ecf8f12469d4d39d35bee1db5d39fc5c168c6f088a6956d"}, + {file = "numpy-2.0.2-cp39-cp39-win32.whl", hash = "sha256:905d16e0c60200656500c95b6b8dca5d109e23cb24abc701d41c02d74c6b3afa"}, + {file = "numpy-2.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:a3f4ab0caa7f053f6797fcd4e1e25caee367db3112ef2b6ef82d749530768c73"}, + {file = "numpy-2.0.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7f0a0c6f12e07fa94133c8a67404322845220c06a9e80e85999afe727f7438b8"}, + {file = "numpy-2.0.2-pp39-pypy39_pp73-macosx_14_0_x86_64.whl", hash = "sha256:312950fdd060354350ed123c0e25a71327d3711584beaef30cdaa93320c392d4"}, + {file = "numpy-2.0.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26df23238872200f63518dd2aa984cfca675d82469535dc7162dc2ee52d9dd5c"}, + {file = "numpy-2.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a46288ec55ebbd58947d31d72be2c63cbf839f0a63b49cb755022310792a3385"}, + {file = "numpy-2.0.2.tar.gz", hash = "sha256:883c987dee1880e2a864ab0dc9892292582510604156762362d9326444636e78"}, ] [[package]] @@ -1052,52 +1063,90 @@ files = [ [[package]] name = "pandas" -version = "1.5.3" +version = "2.2.3" description = "Powerful data structures for data analysis, time series, and statistics" category = "main" optional = false -python-versions = ">=3.8" -files = [ - {file = "pandas-1.5.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3749077d86e3a2f0ed51367f30bf5b82e131cc0f14260c4d3e499186fccc4406"}, - {file = "pandas-1.5.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:972d8a45395f2a2d26733eb8d0f629b2f90bebe8e8eddbb8829b180c09639572"}, - {file = "pandas-1.5.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:50869a35cbb0f2e0cd5ec04b191e7b12ed688874bd05dd777c19b28cbea90996"}, - {file = "pandas-1.5.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3ac844a0fe00bfaeb2c9b51ab1424e5c8744f89860b138434a363b1f620f354"}, - {file = "pandas-1.5.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a0a56cef15fd1586726dace5616db75ebcfec9179a3a55e78f72c5639fa2a23"}, - {file = "pandas-1.5.3-cp310-cp310-win_amd64.whl", hash = "sha256:478ff646ca42b20376e4ed3fa2e8d7341e8a63105586efe54fa2508ee087f328"}, - {file = "pandas-1.5.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6973549c01ca91ec96199e940495219c887ea815b2083722821f1d7abfa2b4dc"}, - {file = "pandas-1.5.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c39a8da13cede5adcd3be1182883aea1c925476f4e84b2807a46e2775306305d"}, - {file = "pandas-1.5.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f76d097d12c82a535fda9dfe5e8dd4127952b45fea9b0276cb30cca5ea313fbc"}, - {file = "pandas-1.5.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e474390e60ed609cec869b0da796ad94f420bb057d86784191eefc62b65819ae"}, - {file = "pandas-1.5.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f2b952406a1588ad4cad5b3f55f520e82e902388a6d5a4a91baa8d38d23c7f6"}, - {file = "pandas-1.5.3-cp311-cp311-win_amd64.whl", hash = "sha256:bc4c368f42b551bf72fac35c5128963a171b40dce866fb066540eeaf46faa003"}, - {file = "pandas-1.5.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:14e45300521902689a81f3f41386dc86f19b8ba8dd5ac5a3c7010ef8d2932813"}, - {file = "pandas-1.5.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9842b6f4b8479e41968eced654487258ed81df7d1c9b7b870ceea24ed9459b31"}, - {file = "pandas-1.5.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:26d9c71772c7afb9d5046e6e9cf42d83dd147b5cf5bcb9d97252077118543792"}, - {file = "pandas-1.5.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5fbcb19d6fceb9e946b3e23258757c7b225ba450990d9ed63ccceeb8cae609f7"}, - {file = "pandas-1.5.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:565fa34a5434d38e9d250af3c12ff931abaf88050551d9fbcdfafca50d62babf"}, - {file = "pandas-1.5.3-cp38-cp38-win32.whl", hash = "sha256:87bd9c03da1ac870a6d2c8902a0e1fd4267ca00f13bc494c9e5a9020920e1d51"}, - {file = "pandas-1.5.3-cp38-cp38-win_amd64.whl", hash = "sha256:41179ce559943d83a9b4bbacb736b04c928b095b5f25dd2b7389eda08f46f373"}, - {file = "pandas-1.5.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c74a62747864ed568f5a82a49a23a8d7fe171d0c69038b38cedf0976831296fa"}, - {file = "pandas-1.5.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c4c00e0b0597c8e4f59e8d461f797e5d70b4d025880516a8261b2817c47759ee"}, - {file = "pandas-1.5.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a50d9a4336a9621cab7b8eb3fb11adb82de58f9b91d84c2cd526576b881a0c5a"}, - {file = "pandas-1.5.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd05f7783b3274aa206a1af06f0ceed3f9b412cf665b7247eacd83be41cf7bf0"}, - {file = "pandas-1.5.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f69c4029613de47816b1bb30ff5ac778686688751a5e9c99ad8c7031f6508e5"}, - {file = "pandas-1.5.3-cp39-cp39-win32.whl", hash = "sha256:7cec0bee9f294e5de5bbfc14d0573f65526071029d036b753ee6507d2a21480a"}, - {file = "pandas-1.5.3-cp39-cp39-win_amd64.whl", hash = "sha256:dfd681c5dc216037e0b0a2c821f5ed99ba9f03ebcf119c7dac0e9a7b960b9ec9"}, - {file = "pandas-1.5.3.tar.gz", hash = "sha256:74a3fd7e5a7ec052f183273dc7b0acd3a863edf7520f5d3a1765c04ffdb3b0b1"}, +python-versions = ">=3.9" +files = [ + {file = "pandas-2.2.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1948ddde24197a0f7add2bdc4ca83bf2b1ef84a1bc8ccffd95eda17fd836ecb5"}, + {file = "pandas-2.2.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:381175499d3802cde0eabbaf6324cce0c4f5d52ca6f8c377c29ad442f50f6348"}, + {file = "pandas-2.2.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d9c45366def9a3dd85a6454c0e7908f2b3b8e9c138f5dc38fed7ce720d8453ed"}, + {file = "pandas-2.2.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86976a1c5b25ae3f8ccae3a5306e443569ee3c3faf444dfd0f41cda24667ad57"}, + {file = "pandas-2.2.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b8661b0238a69d7aafe156b7fa86c44b881387509653fdf857bebc5e4008ad42"}, + {file = "pandas-2.2.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:37e0aced3e8f539eccf2e099f65cdb9c8aa85109b0be6e93e2baff94264bdc6f"}, + {file = "pandas-2.2.3-cp310-cp310-win_amd64.whl", hash = "sha256:56534ce0746a58afaf7942ba4863e0ef81c9c50d3f0ae93e9497d6a41a057645"}, + {file = "pandas-2.2.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:66108071e1b935240e74525006034333f98bcdb87ea116de573a6a0dccb6c039"}, + {file = "pandas-2.2.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7c2875855b0ff77b2a64a0365e24455d9990730d6431b9e0ee18ad8acee13dbd"}, + {file = "pandas-2.2.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd8d0c3be0515c12fed0bdbae072551c8b54b7192c7b1fda0ba56059a0179698"}, + {file = "pandas-2.2.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c124333816c3a9b03fbeef3a9f230ba9a737e9e5bb4060aa2107a86cc0a497fc"}, + {file = "pandas-2.2.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:63cc132e40a2e084cf01adf0775b15ac515ba905d7dcca47e9a251819c575ef3"}, + {file = "pandas-2.2.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:29401dbfa9ad77319367d36940cd8a0b3a11aba16063e39632d98b0e931ddf32"}, + {file = "pandas-2.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:3fc6873a41186404dad67245896a6e440baacc92f5b716ccd1bc9ed2995ab2c5"}, + {file = "pandas-2.2.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b1d432e8d08679a40e2a6d8b2f9770a5c21793a6f9f47fdd52c5ce1948a5a8a9"}, + {file = "pandas-2.2.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a5a1595fe639f5988ba6a8e5bc9649af3baf26df3998a0abe56c02609392e0a4"}, + {file = "pandas-2.2.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5de54125a92bb4d1c051c0659e6fcb75256bf799a732a87184e5ea503965bce3"}, + {file = "pandas-2.2.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fffb8ae78d8af97f849404f21411c95062db1496aeb3e56f146f0355c9989319"}, + {file = "pandas-2.2.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6dfcb5ee8d4d50c06a51c2fffa6cff6272098ad6540aed1a76d15fb9318194d8"}, + {file = "pandas-2.2.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:062309c1b9ea12a50e8ce661145c6aab431b1e99530d3cd60640e255778bd43a"}, + {file = "pandas-2.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:59ef3764d0fe818125a5097d2ae867ca3fa64df032331b7e0917cf5d7bf66b13"}, + {file = "pandas-2.2.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f00d1345d84d8c86a63e476bb4955e46458b304b9575dcf71102b5c705320015"}, + {file = "pandas-2.2.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3508d914817e153ad359d7e069d752cdd736a247c322d932eb89e6bc84217f28"}, + {file = "pandas-2.2.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22a9d949bfc9a502d320aa04e5d02feab689d61da4e7764b62c30b991c42c5f0"}, + {file = "pandas-2.2.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3a255b2c19987fbbe62a9dfd6cff7ff2aa9ccab3fc75218fd4b7530f01efa24"}, + {file = "pandas-2.2.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:800250ecdadb6d9c78eae4990da62743b857b470883fa27f652db8bdde7f6659"}, + {file = "pandas-2.2.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6374c452ff3ec675a8f46fd9ab25c4ad0ba590b71cf0656f8b6daa5202bca3fb"}, + {file = "pandas-2.2.3-cp313-cp313-win_amd64.whl", hash = "sha256:61c5ad4043f791b61dd4752191d9f07f0ae412515d59ba8f005832a532f8736d"}, + {file = "pandas-2.2.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3b71f27954685ee685317063bf13c7709a7ba74fc996b84fc6821c59b0f06468"}, + {file = "pandas-2.2.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:38cf8125c40dae9d5acc10fa66af8ea6fdf760b2714ee482ca691fc66e6fcb18"}, + {file = "pandas-2.2.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ba96630bc17c875161df3818780af30e43be9b166ce51c9a18c1feae342906c2"}, + {file = "pandas-2.2.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1db71525a1538b30142094edb9adc10be3f3e176748cd7acc2240c2f2e5aa3a4"}, + {file = "pandas-2.2.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:15c0e1e02e93116177d29ff83e8b1619c93ddc9c49083f237d4312337a61165d"}, + {file = "pandas-2.2.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ad5b65698ab28ed8d7f18790a0dc58005c7629f227be9ecc1072aa74c0c1d43a"}, + {file = "pandas-2.2.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bc6b93f9b966093cb0fd62ff1a7e4c09e6d546ad7c1de191767baffc57628f39"}, + {file = "pandas-2.2.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5dbca4c1acd72e8eeef4753eeca07de9b1db4f398669d5994086f788a5d7cc30"}, + {file = "pandas-2.2.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8cd6d7cc958a3910f934ea8dbdf17b2364827bb4dafc38ce6eef6bb3d65ff09c"}, + {file = "pandas-2.2.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99df71520d25fade9db7c1076ac94eb994f4d2673ef2aa2e86ee039b6746d20c"}, + {file = "pandas-2.2.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:31d0ced62d4ea3e231a9f228366919a5ea0b07440d9d4dac345376fd8e1477ea"}, + {file = "pandas-2.2.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7eee9e7cea6adf3e3d24e304ac6b8300646e2a5d1cd3a3c2abed9101b0846761"}, + {file = "pandas-2.2.3-cp39-cp39-win_amd64.whl", hash = "sha256:4850ba03528b6dd51d6c5d273c46f183f39a9baf3f0143e566b89450965b105e"}, + {file = "pandas-2.2.3.tar.gz", hash = "sha256:4f18ba62b61d7e192368b84517265a99b4d7ee8912f8708660fb4a366cc82667"}, ] [package.dependencies] numpy = [ - {version = ">=1.20.3", markers = "python_version < \"3.10\""}, - {version = ">=1.21.0", markers = "python_version >= \"3.10\""}, - {version = ">=1.23.2", markers = "python_version >= \"3.11\""}, + {version = ">=1.22.4", markers = "python_version < \"3.11\""}, + {version = ">=1.23.2", markers = "python_version == \"3.11\""}, + {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, ] -python-dateutil = ">=2.8.1" +python-dateutil = ">=2.8.2" pytz = ">=2020.1" +tzdata = ">=2022.7" [package.extras] -test = ["hypothesis (>=5.5.3)", "pytest (>=6.0)", "pytest-xdist (>=1.31)"] +all = ["PyQt5 (>=5.15.9)", "SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)", "beautifulsoup4 (>=4.11.2)", "bottleneck (>=1.3.6)", "dataframe-api-compat (>=0.1.7)", "fastparquet (>=2022.12.0)", "fsspec (>=2022.11.0)", "gcsfs (>=2022.11.0)", "html5lib (>=1.1)", "hypothesis (>=6.46.1)", "jinja2 (>=3.1.2)", "lxml (>=4.9.2)", "matplotlib (>=3.6.3)", "numba (>=0.56.4)", "numexpr (>=2.8.4)", "odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "pandas-gbq (>=0.19.0)", "psycopg2 (>=2.9.6)", "pyarrow (>=10.0.1)", "pymysql (>=1.0.2)", "pyreadstat (>=1.2.0)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "qtpy (>=2.3.0)", "s3fs (>=2022.11.0)", "scipy (>=1.10.0)", "tables (>=3.8.0)", "tabulate (>=0.9.0)", "xarray (>=2022.12.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)", "zstandard (>=0.19.0)"] +aws = ["s3fs (>=2022.11.0)"] +clipboard = ["PyQt5 (>=5.15.9)", "qtpy (>=2.3.0)"] +compression = ["zstandard (>=0.19.0)"] +computation = ["scipy (>=1.10.0)", "xarray (>=2022.12.0)"] +consortium-standard = ["dataframe-api-compat (>=0.1.7)"] +excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)"] +feather = ["pyarrow (>=10.0.1)"] +fss = ["fsspec (>=2022.11.0)"] +gcp = ["gcsfs (>=2022.11.0)", "pandas-gbq (>=0.19.0)"] +hdf5 = ["tables (>=3.8.0)"] +html = ["beautifulsoup4 (>=4.11.2)", "html5lib (>=1.1)", "lxml (>=4.9.2)"] +mysql = ["SQLAlchemy (>=2.0.0)", "pymysql (>=1.0.2)"] +output-formatting = ["jinja2 (>=3.1.2)", "tabulate (>=0.9.0)"] +parquet = ["pyarrow (>=10.0.1)"] +performance = ["bottleneck (>=1.3.6)", "numba (>=0.56.4)", "numexpr (>=2.8.4)"] +plot = ["matplotlib (>=3.6.3)"] +postgresql = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "psycopg2 (>=2.9.6)"] +pyarrow = ["pyarrow (>=10.0.1)"] +spss = ["pyreadstat (>=1.2.0)"] +sql-other = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)"] +test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)"] +xml = ["lxml (>=4.9.2)"] [[package]] name = "pathspec" @@ -1146,52 +1195,58 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "pyarrow" -version = "14.0.2" +version = "18.1.0" description = "Python library for Apache Arrow" category = "main" optional = false -python-versions = ">=3.8" -files = [ - {file = "pyarrow-14.0.2-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:ba9fe808596c5dbd08b3aeffe901e5f81095baaa28e7d5118e01354c64f22807"}, - {file = "pyarrow-14.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:22a768987a16bb46220cef490c56c671993fbee8fd0475febac0b3e16b00a10e"}, - {file = "pyarrow-14.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2dbba05e98f247f17e64303eb876f4a80fcd32f73c7e9ad975a83834d81f3fda"}, - {file = "pyarrow-14.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a898d134d00b1eca04998e9d286e19653f9d0fcb99587310cd10270907452a6b"}, - {file = "pyarrow-14.0.2-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:87e879323f256cb04267bb365add7208f302df942eb943c93a9dfeb8f44840b1"}, - {file = "pyarrow-14.0.2-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:76fc257559404ea5f1306ea9a3ff0541bf996ff3f7b9209fc517b5e83811fa8e"}, - {file = "pyarrow-14.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:b0c4a18e00f3a32398a7f31da47fefcd7a927545b396e1f15d0c85c2f2c778cd"}, - {file = "pyarrow-14.0.2-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:87482af32e5a0c0cce2d12eb3c039dd1d853bd905b04f3f953f147c7a196915b"}, - {file = "pyarrow-14.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:059bd8f12a70519e46cd64e1ba40e97eae55e0cbe1695edd95384653d7626b23"}, - {file = "pyarrow-14.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f16111f9ab27e60b391c5f6d197510e3ad6654e73857b4e394861fc79c37200"}, - {file = "pyarrow-14.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06ff1264fe4448e8d02073f5ce45a9f934c0f3db0a04460d0b01ff28befc3696"}, - {file = "pyarrow-14.0.2-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:6dd4f4b472ccf4042f1eab77e6c8bce574543f54d2135c7e396f413046397d5a"}, - {file = "pyarrow-14.0.2-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:32356bfb58b36059773f49e4e214996888eeea3a08893e7dbde44753799b2a02"}, - {file = "pyarrow-14.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:52809ee69d4dbf2241c0e4366d949ba035cbcf48409bf404f071f624ed313a2b"}, - {file = "pyarrow-14.0.2-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:c87824a5ac52be210d32906c715f4ed7053d0180c1060ae3ff9b7e560f53f944"}, - {file = "pyarrow-14.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a25eb2421a58e861f6ca91f43339d215476f4fe159eca603c55950c14f378cc5"}, - {file = "pyarrow-14.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c1da70d668af5620b8ba0a23f229030a4cd6c5f24a616a146f30d2386fec422"}, - {file = "pyarrow-14.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2cc61593c8e66194c7cdfae594503e91b926a228fba40b5cf25cc593563bcd07"}, - {file = "pyarrow-14.0.2-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:78ea56f62fb7c0ae8ecb9afdd7893e3a7dbeb0b04106f5c08dbb23f9c0157591"}, - {file = "pyarrow-14.0.2-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:37c233ddbce0c67a76c0985612fef27c0c92aef9413cf5aa56952f359fcb7379"}, - {file = "pyarrow-14.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:e4b123ad0f6add92de898214d404e488167b87b5dd86e9a434126bc2b7a5578d"}, - {file = "pyarrow-14.0.2-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:e354fba8490de258be7687f341bc04aba181fc8aa1f71e4584f9890d9cb2dec2"}, - {file = "pyarrow-14.0.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:20e003a23a13da963f43e2b432483fdd8c38dc8882cd145f09f21792e1cf22a1"}, - {file = "pyarrow-14.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc0de7575e841f1595ac07e5bc631084fd06ca8b03c0f2ecece733d23cd5102a"}, - {file = "pyarrow-14.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66e986dc859712acb0bd45601229021f3ffcdfc49044b64c6d071aaf4fa49e98"}, - {file = "pyarrow-14.0.2-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:f7d029f20ef56673a9730766023459ece397a05001f4e4d13805111d7c2108c0"}, - {file = "pyarrow-14.0.2-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:209bac546942b0d8edc8debda248364f7f668e4aad4741bae58e67d40e5fcf75"}, - {file = "pyarrow-14.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:1e6987c5274fb87d66bb36816afb6f65707546b3c45c44c28e3c4133c010a881"}, - {file = "pyarrow-14.0.2-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:a01d0052d2a294a5f56cc1862933014e696aa08cc7b620e8c0cce5a5d362e976"}, - {file = "pyarrow-14.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a51fee3a7db4d37f8cda3ea96f32530620d43b0489d169b285d774da48ca9785"}, - {file = "pyarrow-14.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64df2bf1ef2ef14cee531e2dfe03dd924017650ffaa6f9513d7a1bb291e59c15"}, - {file = "pyarrow-14.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c0fa3bfdb0305ffe09810f9d3e2e50a2787e3a07063001dcd7adae0cee3601a"}, - {file = "pyarrow-14.0.2-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:c65bf4fd06584f058420238bc47a316e80dda01ec0dfb3044594128a6c2db794"}, - {file = "pyarrow-14.0.2-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:63ac901baec9369d6aae1cbe6cca11178fb018a8d45068aaf5bb54f94804a866"}, - {file = "pyarrow-14.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:75ee0efe7a87a687ae303d63037d08a48ef9ea0127064df18267252cfe2e9541"}, - {file = "pyarrow-14.0.2.tar.gz", hash = "sha256:36cef6ba12b499d864d1def3e990f97949e0b79400d08b7cf74504ffbd3eb025"}, +python-versions = ">=3.9" +files = [ + {file = "pyarrow-18.1.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:e21488d5cfd3d8b500b3238a6c4b075efabc18f0f6d80b29239737ebd69caa6c"}, + {file = "pyarrow-18.1.0-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:b516dad76f258a702f7ca0250885fc93d1fa5ac13ad51258e39d402bd9e2e1e4"}, + {file = "pyarrow-18.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f443122c8e31f4c9199cb23dca29ab9427cef990f283f80fe15b8e124bcc49b"}, + {file = "pyarrow-18.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0a03da7f2758645d17b7b4f83c8bffeae5bbb7f974523fe901f36288d2eab71"}, + {file = "pyarrow-18.1.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:ba17845efe3aa358ec266cf9cc2800fa73038211fb27968bfa88acd09261a470"}, + {file = "pyarrow-18.1.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:3c35813c11a059056a22a3bef520461310f2f7eea5c8a11ef9de7062a23f8d56"}, + {file = "pyarrow-18.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:9736ba3c85129d72aefa21b4f3bd715bc4190fe4426715abfff90481e7d00812"}, + {file = "pyarrow-18.1.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:eaeabf638408de2772ce3d7793b2668d4bb93807deed1725413b70e3156a7854"}, + {file = "pyarrow-18.1.0-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:3b2e2239339c538f3464308fd345113f886ad031ef8266c6f004d49769bb074c"}, + {file = "pyarrow-18.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f39a2e0ed32a0970e4e46c262753417a60c43a3246972cfc2d3eb85aedd01b21"}, + {file = "pyarrow-18.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e31e9417ba9c42627574bdbfeada7217ad8a4cbbe45b9d6bdd4b62abbca4c6f6"}, + {file = "pyarrow-18.1.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:01c034b576ce0eef554f7c3d8c341714954be9b3f5d5bc7117006b85fcf302fe"}, + {file = "pyarrow-18.1.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:f266a2c0fc31995a06ebd30bcfdb7f615d7278035ec5b1cd71c48d56daaf30b0"}, + {file = "pyarrow-18.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:d4f13eee18433f99adefaeb7e01d83b59f73360c231d4782d9ddfaf1c3fbde0a"}, + {file = "pyarrow-18.1.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:9f3a76670b263dc41d0ae877f09124ab96ce10e4e48f3e3e4257273cee61ad0d"}, + {file = "pyarrow-18.1.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:da31fbca07c435be88a0c321402c4e31a2ba61593ec7473630769de8346b54ee"}, + {file = "pyarrow-18.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:543ad8459bc438efc46d29a759e1079436290bd583141384c6f7a1068ed6f992"}, + {file = "pyarrow-18.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0743e503c55be0fdb5c08e7d44853da27f19dc854531c0570f9f394ec9671d54"}, + {file = "pyarrow-18.1.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:d4b3d2a34780645bed6414e22dda55a92e0fcd1b8a637fba86800ad737057e33"}, + {file = "pyarrow-18.1.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:c52f81aa6f6575058d8e2c782bf79d4f9fdc89887f16825ec3a66607a5dd8e30"}, + {file = "pyarrow-18.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:0ad4892617e1a6c7a551cfc827e072a633eaff758fa09f21c4ee548c30bcaf99"}, + {file = "pyarrow-18.1.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:84e314d22231357d473eabec709d0ba285fa706a72377f9cc8e1cb3c8013813b"}, + {file = "pyarrow-18.1.0-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:f591704ac05dfd0477bb8f8e0bd4b5dc52c1cadf50503858dce3a15db6e46ff2"}, + {file = "pyarrow-18.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:acb7564204d3c40babf93a05624fc6a8ec1ab1def295c363afc40b0c9e66c191"}, + {file = "pyarrow-18.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:74de649d1d2ccb778f7c3afff6085bd5092aed4c23df9feeb45dd6b16f3811aa"}, + {file = "pyarrow-18.1.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:f96bd502cb11abb08efea6dab09c003305161cb6c9eafd432e35e76e7fa9b90c"}, + {file = "pyarrow-18.1.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:36ac22d7782554754a3b50201b607d553a8d71b78cdf03b33c1125be4b52397c"}, + {file = "pyarrow-18.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:25dbacab8c5952df0ca6ca0af28f50d45bd31c1ff6fcf79e2d120b4a65ee7181"}, + {file = "pyarrow-18.1.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:6a276190309aba7bc9d5bd2933230458b3521a4317acfefe69a354f2fe59f2bc"}, + {file = "pyarrow-18.1.0-cp313-cp313t-macosx_12_0_x86_64.whl", hash = "sha256:ad514dbfcffe30124ce655d72771ae070f30bf850b48bc4d9d3b25993ee0e386"}, + {file = "pyarrow-18.1.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aebc13a11ed3032d8dd6e7171eb6e86d40d67a5639d96c35142bd568b9299324"}, + {file = "pyarrow-18.1.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d6cf5c05f3cee251d80e98726b5c7cc9f21bab9e9783673bac58e6dfab57ecc8"}, + {file = "pyarrow-18.1.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:11b676cd410cf162d3f6a70b43fb9e1e40affbc542a1e9ed3681895f2962d3d9"}, + {file = "pyarrow-18.1.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:b76130d835261b38f14fc41fdfb39ad8d672afb84c447126b84d5472244cfaba"}, + {file = "pyarrow-18.1.0-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:0b331e477e40f07238adc7ba7469c36b908f07c89b95dd4bd3a0ec84a3d1e21e"}, + {file = "pyarrow-18.1.0-cp39-cp39-macosx_12_0_x86_64.whl", hash = "sha256:2c4dd0c9010a25ba03e198fe743b1cc03cd33c08190afff371749c52ccbbaf76"}, + {file = "pyarrow-18.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f97b31b4c4e21ff58c6f330235ff893cc81e23da081b1a4b1c982075e0ed4e9"}, + {file = "pyarrow-18.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a4813cb8ecf1809871fd2d64a8eff740a1bd3691bbe55f01a3cf6c5ec869754"}, + {file = "pyarrow-18.1.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:05a5636ec3eb5cc2a36c6edb534a38ef57b2ab127292a716d00eabb887835f1e"}, + {file = "pyarrow-18.1.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:73eeed32e724ea3568bb06161cad5fa7751e45bc2228e33dcb10c614044165c7"}, + {file = "pyarrow-18.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:a1880dd6772b685e803011a6b43a230c23b566859a6e0c9a276c1e0faf4f4052"}, + {file = "pyarrow-18.1.0.tar.gz", hash = "sha256:9386d3ca9c145b5539a1cfc75df07757dff870168c959b473a0bccbc3abc8c73"}, ] -[package.dependencies] -numpy = ">=1.16.6" +[package.extras] +test = ["cffi", "hypothesis", "pandas", "pytest", "pytz"] [[package]] name = "pydantic" @@ -1352,14 +1407,14 @@ testutils = ["gitpython (>3)"] [[package]] name = "pytest" -version = "7.4.4" +version = "8.3.4" description = "pytest: simple powerful testing with Python" category = "dev" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, - {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, + {file = "pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6"}, + {file = "pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761"}, ] [package.dependencies] @@ -1367,11 +1422,11 @@ colorama = {version = "*", markers = "sys_platform == \"win32\""} exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} iniconfig = "*" packaging = "*" -pluggy = ">=0.12,<2.0" -tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} +pluggy = ">=1.5,<2" +tomli = {version = ">=1", markers = "python_version < \"3.11\""} [package.extras] -testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] [[package]] name = "pytest-cov" @@ -1815,6 +1870,18 @@ files = [ {file = "typing_extensions-4.11.0.tar.gz", hash = "sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0"}, ] +[[package]] +name = "tzdata" +version = "2024.2" +description = "Provider of IANA time zone data" +category = "main" +optional = false +python-versions = ">=2" +files = [ + {file = "tzdata-2024.2-py2.py3-none-any.whl", hash = "sha256:a48093786cdcde33cad18c2555e8532f34422074448fbc874186f0abd79565cd"}, + {file = "tzdata-2024.2.tar.gz", hash = "sha256:7d85cc416e9382e69095b7bdf4afd9e3880418a2413feec7069d533d6b4e31cc"}, +] + [[package]] name = "urllib3" version = "1.26.19" @@ -2297,5 +2364,5 @@ docs = [] [metadata] lock-version = "2.0" -python-versions = "^3.8" -content-hash = "cbb2237162133d2fcd62f84a7188b8b4107564affa74d9da0dd312d51bb68292" +python-versions = "^3.9" +content-hash = "cc74e15b393d53c89515f04d374d6c75cc3804ecbc1480d0353574d28e1c55bd" diff --git a/pyproject.toml b/pyproject.toml index 6de51bf..bb65ec4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "ipumspy" -version = "0.5.1" +version = "0.6.0" description = "A collection of tools for working with IPUMS data" authors = ["Kevin H. Wilson ", "Renae Rodgers "] @@ -10,11 +10,11 @@ packages = [ ] [tool.poetry.dependencies] -python = "^3.8" -pandas = "^1.5.2" -numpy = "^1.24.4" +python = "^3.9" +pandas = "^2.2.3" +numpy = "^2.0.0" click = "^8.0.0" -pyarrow = "^14.0.2" +pyarrow = "^18.1.0" requests = {extras = ["use_chardet_on_py3"], version = "^2.26.0"} importlib-metadata = "^4.13.0" PyYAML = "^6.0.1" @@ -26,7 +26,7 @@ isort = "^5.8.0" mypy = "^1.0.1" [tool.poetry.group.test.dependencies] -pytest = "^7.2.2" +pytest = "^8.3.4" pytest-cov = "^4.0.0" python-dotenv = "^1.0.0" fastapi = "^0.109.2" From 08b291d5e680e805609d23e9dbcd3e5a0d1fb454 Mon Sep 17 00:00:00 2001 From: renae-r Date: Mon, 13 Jan 2025 11:07:30 -0600 Subject: [PATCH 33/35] update change log --- docs/source/change-log.rst | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/docs/source/change-log.rst b/docs/source/change-log.rst index cdd284d..c4f94a2 100644 --- a/docs/source/change-log.rst +++ b/docs/source/change-log.rst @@ -11,6 +11,25 @@ This project adheres to `Semantic Versioning`_. .. _Semantic Versioning: http://semver.org/ +0.6.0 +----- + +* New Features + + * Support for IPUMS NHGIS extract API, including + + * :py:class:`~ipumspy.api.extract.AggregateDataExtract` class has been added to support IPUMS NHGIS extracts + * Support for downloading IPUMS NHGIS supplemental datasets via API + + * Support for IPUMS NHGIS metadata API, including + + * :py:meth:`~ipumspy.api.IpumsApiClient.get_metadata()` method to request IPUMS NHGIS metadata from the IPUMS API + * :py:class:`~ipumspy.api.metadata.DatasetMetadata`, :py:class:`~ipumspy.api.metadata.TimeSeriesTableMetadata`, and :py:class:`~ipumspy.api.metadata.DataTableMetadata` classes to request and store metadata for different types of IPUMS NHGIS data + * :py:meth:`~ipumspy.api.IpumsApiClient.get_metadata_catalog()` generator to retrieve an inventory for a given type of metadata resource. + + * Added :py:class:`~ipumspy.api.exceptions.IpumsApiRateLimitException` for requests that exceed the IPUMS API rate limit. + + 0.5.1 ----- 2024-07-01 From 7846d91b4dcdd5bfa32e6dd71ef9c7b5175d4434 Mon Sep 17 00:00:00 2001 From: Finn Roberts Date: Mon, 13 Jan 2025 14:54:33 -0500 Subject: [PATCH 34/35] more change log updates --- docs/source/change-log.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/source/change-log.rst b/docs/source/change-log.rst index c4f94a2..464100d 100644 --- a/docs/source/change-log.rst +++ b/docs/source/change-log.rst @@ -19,6 +19,8 @@ This project adheres to `Semantic Versioning`_. * Support for IPUMS NHGIS extract API, including * :py:class:`~ipumspy.api.extract.AggregateDataExtract` class has been added to support IPUMS NHGIS extracts + * :py:class:`~ipumspy.api.extract.Dataset`, :py:class:`~ipumspy.api.extract.TimeSeriesTable`, and + :py:class:`~ipumspy.api.extract.Shapefile` classes have been added for use when constructing IPUMS NHGIS extract requests. * Support for downloading IPUMS NHGIS supplemental datasets via API * Support for IPUMS NHGIS metadata API, including @@ -29,6 +31,9 @@ This project adheres to `Semantic Versioning`_. * Added :py:class:`~ipumspy.api.exceptions.IpumsApiRateLimitException` for requests that exceed the IPUMS API rate limit. +* Bug Fixes + + * Fixed bug in ``initial_wait_time`` argument that prevented the use of ``submit-and-download`` command via the command line 0.5.1 ----- From e44b092b4a246c89d7efbd5ff0ded4a7c9f9da41 Mon Sep 17 00:00:00 2001 From: renae-r Date: Tue, 21 Jan 2025 11:20:27 -0600 Subject: [PATCH 35/35] remove 3.13 from main.yml --- .github/workflows/main.yml | 2 +- docs/source/change-log.rst | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a2b0876..7860b56 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -13,7 +13,7 @@ jobs: strategy: fail-fast: true matrix: - python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] + python-version: ["3.9", "3.10", "3.11", "3.12"] steps: - uses: actions/checkout@v4 diff --git a/docs/source/change-log.rst b/docs/source/change-log.rst index 464100d..f22f10b 100644 --- a/docs/source/change-log.rst +++ b/docs/source/change-log.rst @@ -33,7 +33,9 @@ This project adheres to `Semantic Versioning`_. * Bug Fixes - * Fixed bug in ``initial_wait_time`` argument that prevented the use of ``submit-and-download`` command via the command line + * Fixed bug in ``initial_wait_time`` argument that prevented the use of ``submit-and-download`` command via the command line interface + +* The minimum supported Python version is now 3.9 0.5.1 -----