Repository: fitnr/censusgeocode (language: python)

Issue:
Allow "zipcode" in addressbatch()
Currently, the `address()` function expects the keyword argument "zipcode". However, the `addressbatch()` function, if you use the iterable-of-dicts form of argument, expects each dict to have the key "zip". This makes it so users of this library have to use two different schemas, "zip" in one place and "zipcode" in another, depending on if they use `address()` or `addressbatch()`.

This tweaks `addressbatch()` so if you use an iterable-of-dicts, you can use the key "zipcode" instead of "zip". Now in my user code, I only need to have one schema, the one with "zipcode".

This change *doe not* affect `addressbatch()` when you pass in a CSV file. One, I didn't see an easy way to fix this (first thought would be to wrap the file-like-object in a custom file-like-object that modifies the contents on every call to `read()`). Two, I'd imagine the need isn't as serious here, because if you're using a file-like-object then I'd think that you wouldn't even be writing the code that formats that data.


Discussion / hints:

Fix patch:
diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml
new file mode 100644
index 0000000..605e09b
--- /dev/null
+++ b/.github/workflows/python.yml
@@ -0,0 +1,66 @@
+# This file is part of censusgeocode.
+# https://github.com/fitnr/censusgeocode
+
+# Licensed under the General Public License (version 3)
+# http://opensource.org/licenses/LGPL-3.0
+# Copyright (c) 2015-7, Neil Freeman <contact@fakeisthenewreal.org>
+
+name: Test and deploy
+
+on:
+  push:
+  pull_request:
+    branches: [master]
+
+jobs:
+  test:
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        python: ["3.7", "3.8", "3.9", "3.10"]
+
+    steps:
+      - uses: actions/checkout@v2
+
+      - name: Set up Python
+        uses: actions/setup-python@v2
+        with:
+          python-version: ${{ matrix.python }}
+          cache: 'pip'
+          cache-dependency-path: setup.cfg
+
+      - name: Install package
+        run: |
+          python -m pip install -U pip
+          pip install '.[tests]'
+
+      - run: make test
+
+  deploy:
+    needs: test
+    if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
+    environment: production
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v2
+
+      - name: Set up Python
+        uses: actions/setup-python@v2
+        with:
+          python-version: "3.10"
+          cache: 'pip'
+          cache-dependency-path: setup.cfg
+
+      - name: Install build requirements
+        run: |
+          python -m pip install -U pip
+          pip install build
+
+      - run: make build
+
+      - name: Publish package
+        if: startsWith(github.ref, 'refs/tags')
+        uses: pypa/gh-action-pypi-publish@release/v1
+        with:
+          user: __token__
+          password: ${{ secrets.PYPI_API_TOKEN }}
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 59379fa..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,34 +0,0 @@
-# This file is part of censusgeocode.
-# https://github.com/fitnr/censusgeocode
-
-# Licensed under the General Public License (version 3)
-# http://opensource.org/licenses/LGPL-3.0
-# Copyright (c) 2015-7, Neil Freeman <contact@fakeisthenewreal.org>
-
-language: python
-
-python:
-    - 3.6
-    - 3.7
-    - 3.8
-    - 3.9
-    - 3.10
-
-matrix:
-    fast_finish: true
-    allow_failures:
-        - python: 3.10
-
-before_install:
-    - pip install coverage vcrpy
-
-install:
-    - python setup.py install
-
-script:
-    - coverage run --source=censusgeocode -m unittest
-    - censusgeocode --version
-    - censusgeocode --csv tests/fixtures/batch.csv || echo failed
-
-after_success:
-    - coverage report
diff --git a/HISTORY.rst b/HISTORY
similarity index 80%
rename from HISTORY.rst
rename to HISTORY
index 64726fc..889dc40 100644
--- a/HISTORY.rst
+++ b/HISTORY
@@ -1,44 +1,43 @@
-0.5.1
+0.5.2
 -----
+* Accept either "zip" or "zipcode" as an argument in `CensusGeocode.address` (#24).
+* Add warning to `CensusGeocode.batch()` when trying send more than 10,000 records
+* Move packaging metadata to setup.cfg and pyproject.toml
+* Use github actions for automated tests
 
+0.5.1
+-----
 * Fix empty default values (#23)
 
 0.5.0
 -----
-
 * Removed hard-coded benchmarks and vintages, which were out of date. Refer to Census Geocoder documentation for a list of active benchmarks and vintages.
 
 0.4.4
 -----
-
 * Handle misformatted batch responses from the Census
 * Add more options for command line tool
 * Codify support for Python 3.7, 3.8. Drop support for Python 3.4
 
 0.4.3
 -----
-
 * Fixed bug in parsing addressbatch results in some versions of Python (thanks @summonholmes)
 
 0.4.2
 -----
-
 * Fix reversed coordinate order in batch addresses
 
 0.4.1
 -----
-
 * Add static methods to module
 * Update vintages and benchmarks.
 
 0.4.0
 -----
-
 * Add support for address batch functionality, including to command line tool
 
 0.3.0
 -----
-
 * Better error reporting when API is down.
 * Use `requests[security]` to fix SSL 3 errors, bump required `requests` version.
 * Combine Py 2 and Py 3 codebases.
@@ -46,16 +45,13 @@
 
 0.2.3
 -----
-
 * Always yield a `ValueError` when Census API is broken.
 * Internal refactor to simplify keyword arguments.
 
 0.2.2
 -----
-
 * Improve deployment and fix readme
 
 0.2.1
 -----
-
 * Add command line tool
diff --git a/MANIFEST.in b/MANIFEST.in
deleted file mode 100644
index 7d43f80..0000000
--- a/MANIFEST.in
+++ /dev/null
@@ -1,2 +0,0 @@
-include *.md
-include LICENSE
diff --git a/Makefile b/Makefile
index e15f80b..45364b5 100644
--- a/Makefile
+++ b/Makefile
@@ -7,15 +7,14 @@
 
 .PHONY: install build upload clean deploy test
 
-install: ; python setup.py install
+install: ; pip install .
 
-test: ; python setup.py test
+test: ; python -m unittest tests/test_*.py
 
 deploy: build
 	twine upload dist/*
 
 build: | clean
-	python setup.py bdist_wheel --universal
-	python setup.py sdist
+	python -m build
 
 clean:; rm -rf dist build
diff --git a/README.md b/README.md
index 487e85a..8aae7d0 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,9 @@
 Census Geocode
 --------------
 
-Census Geocode is a light weight Python wrapper for the US Census [Geocoder API](http://geocoding.geo.census.gov/geocoder/), compatible with both Python 2 and 3. It comes packaged with a simple command line tool for geocoding an address to a longitude and latitude, or a batch file into a parsed address and coordinates.
+Census Geocode is a light weight Python wrapper for the US Census [Geocoder API](http://geocoding.geo.census.gov/geocoder/), compatible with  Python 3. It comes packaged with a simple command line tool for geocoding an address to a longitude and latitude, or a batch file into a parsed address and coordinates.
 
-It's strongly recommended to review the [Census Geocoder docs](https://geocoding.geo.census.gov/geocoder/Geocoding_Services_API.pdf) before using this module.
+It's strongly recommended to review the [Census Geocoder docs](https://www.census.gov/programs-surveys/geography/technical-documentation/complete-technical-documentation/census-geocoder.html) before using this module.
 
 Basic example:
 
@@ -12,7 +12,7 @@ import censusgeocode as cg
 
 cg.coordinates(x=-76, y=41)
 cg.onelineaddress('1600 Pennsylvania Avenue, Washington, DC')
-cg.address('1600 Pennsylvania Avenue', city='Washington', state='DC', zipcode='20006')
+cg.address('1600 Pennsylvania Avenue', city='Washington', state='DC', zip='20006')
 cg.addressbatch('data/addresses.csv')
 ```
 
@@ -70,18 +70,7 @@ Queries return a CensusResult object, which is basically a Python list with an e
         'TRACT': '216600'
     }],
     'Census Tracts': [{
-        'AREALAND': 86404594,
-        'AREAWATER': 650526,
-        'BASENAME': '2166',
-        'CENTLAT': '+41.0361462',
-        'CENTLON': '-075.9801252',
-        'COUNTY': '079',
-        'FUNCSTAT': 'S',
-        'GEOID': '42079216600',
-        'INTPTLAT': '+41.0379841',
-        'INTPTLON': '-075.9743749',
-        'LSADC': 'CT',
-        'MTFCC': 'G5020',
+        # snip 
         'NAME': 'Census Tract 2166',
         'OBJECTID': 61245,
         'OID': 20790277158250,
@@ -89,41 +78,15 @@ Queries return a CensusResult object, which is basically a Python list with an e
         'TRACT': '216600'
     }],
     'Counties': [{
-        'AREALAND': 2305974186,
-        'AREAWATER': 41240020,
-        'BASENAME': 'Luzerne',
-        'CENTLAT': '+41.1768961',
-        'CENTLON': '-075.9890400',
-        'COUNTY': '079',
-        'COUNTYCC': 'H1',
-        'COUNTYNS': '01209183',
-        'FUNCSTAT': 'A',
-        'GEOID': '42079',
-        'INTPTLAT': '+41.1727868',
-        'INTPTLON': '-075.9760345',
-        'LSADC': '06',
-        'MTFCC': 'G4020',
+        # snip
         'NAME': 'Luzerne County',
         'OBJECTID': 866,
         'OID': 27590277115518,
         'STATE': '42'
     }],
     'States': [{
-        'AREALAND': 115884236236,
-        'AREAWATER': 3395797284,
-        'BASENAME': 'Pennsylvania',
-        'CENTLAT': '+40.9011252',
-        'CENTLON': '-077.8369164',
-        'DIVISION': '2',
-        'FUNCSTAT': 'A',
-        'GEOID': '42',
-        'INTPTLAT': '+40.9024957',
-        'INTPTLON': '-077.8334514',
-        'LSADC': '00',
-        'MTFCC': 'G4000',
+        # snip
         'NAME': 'Pennsylvania',
-        'OBJECTID': 37,
-        'OID': 27490163788605,
         'REGION': '1',
         'STATE': '42',
         'STATENS': '01779798',
@@ -136,8 +99,8 @@ Queries return a CensusResult object, which is basically a Python list with an e
 
 By default, the geocoder uses the "Current" vintage and benchmarks. To use another vintage or benchmark, use the `CensusGeocode` class:
 ````python
-import censusgeocode
-cg = censusgeocode.CensusGeocode(benchmark='Public_AR_Current', vintage='Census2020_Current')
+from censusgeocode import CensusGeocode
+cg = CensusGeocode(benchmark='Public_AR_Current', vintage='Census2020_Current')
 cg.onelineaddress(foobar)
 ````
 
@@ -175,7 +138,7 @@ censusgeocode --csv tests/fixtures/batch.csv
 
 Or from stdin, using `-` as the filename:
 ````
-cat tests/fixtures/batch.csv | censusgeocode --csv -
+head tests/fixtures/batch.csv | censusgeocode --csv -
 ````
 
 According to the Census docs, the batch geocoder is limited to 10,000 rows.
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..3c60ccf
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,5 @@
+# This file is part of censusgeocode.
+# https://github.com/fitnr/censusgeocode
+[build-system]
+requires = ["setuptools>=60.5.0", "wheel"]
+build-backend = 'setuptools.build_meta'
diff --git a/setup.cfg b/setup.cfg
index 665de89..b4710c4 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,5 +1,42 @@
-[bdist_wheel]
-universal = 1
-
+# -*- coding: utf-8 -*-
 [metadata]
 license_files = LICENSE.txt
+name = censusgeocode
+version = attr: censusgeocode.__version__
+description = Thin Python wrapper for the US Census Geocoder
+long_description = file: README.md
+long_description_content_type = text/markdown
+keywords= census, geocode, api
+author = Neil Freeman
+author_email = contact@fakeisthenewreal.org
+url = https://github.com/fitnr/censusgeocode
+license = GNU General Public License v3 (GPLv3)
+classifiers =
+    Development Status :: 4 - Beta
+    Intended Audience :: Developers
+    License :: OSI Approved :: GNU General Public License v3 (GPLv3)
+    Natural Language :: English
+    Programming Language :: Python :: 3.7
+    Programming Language :: Python :: 3.8
+    Programming Language :: Python :: 3.9
+    Programming Language :: Python :: 3.10
+    Operating System :: OS Independent
+
+[options]
+package_dir =
+    =src
+packages = find:
+install_requires = 
+	requests[security]>=2.27.0,<3
+    requests-toolbelt>=0.9.0,<1
+python_requires = >= 3.6, <4
+
+[options.extras_require]
+tests = vcrpy>=4.1.1,<5
+
+[options.packages.find]
+where = src
+
+[options.entry_points]
+console_scripts =
+   censusgeocode = censusgeocode.__main__:main
diff --git a/setup.py b/setup.py
index 16eb33e..211a2af 100644
--- a/setup.py
+++ b/setup.py
@@ -10,47 +10,4 @@
 
 from setuptools import setup
 
-with open('censusgeocode/__init__.py') as i:
-    version = next(r for r in i.readlines() if '__version__' in r).split('=')[1].strip('"\' \n')
-
-with open('README.md') as f:
-    readme = f.read()
-
-setup(
-    name='censusgeocode',
-    version=version,
-    description='Thin Python wrapper for the US Census Geocoder',
-    long_description=readme,
-    long_description_content_type='text/markdown',
-    keywords='census geocode api',
-    author='Neil Freeman',
-    author_email='contact@fakeisthenewreal.org',
-    url='https://github.com/fitnr/censusgeocode',
-    license='GNU General Public License v3 (GPLv3)',
-    classifiers=[
-        'Development Status :: 4 - Beta',
-        'Intended Audience :: Developers',
-        'License :: OSI Approved :: GNU General Public License v3 (GPLv3)',
-        'Natural Language :: English',
-        'Programming Language :: Python :: 3.6',
-        'Programming Language :: Python :: 3.7',
-        'Programming Language :: Python :: 3.8',
-        'Programming Language :: Python :: 3.9',
-        'Operating System :: OS Independent',
-    ],
-    install_requires=[
-        'requests[security]>=2.18,<3',
-        'requests-toolbelt >=0.8.0, <1',
-        'six'
-    ],
-    tests_require=[
-        'vcrpy>=1.11.1, <1.12',
-    ],
-    packages=['censusgeocode'],
-    test_suite='tests',
-    entry_points={
-        'console_scripts': [
-            'censusgeocode=censusgeocode.__main__:main',
-        ],
-    },
-)
+setup()
diff --git a/censusgeocode/__init__.py b/src/censusgeocode/__init__.py
similarity index 91%
rename from censusgeocode/__init__.py
rename to src/censusgeocode/__init__.py
index 4ccfc52..df18b7b 100644
--- a/censusgeocode/__init__.py
+++ b/src/censusgeocode/__init__.py
@@ -1,4 +1,3 @@
-#!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
 # This file is part of censusgeocode.
@@ -10,7 +9,7 @@
 
 from .censusgeocode import CensusGeocode
 
-__version__ = '0.5.1'
+__version__ = '0.5.2'
 
 cg = CensusGeocode()
 
diff --git a/censusgeocode/__main__.py b/src/censusgeocode/__main__.py
similarity index 96%
rename from censusgeocode/__main__.py
rename to src/censusgeocode/__main__.py
index 6e28a4f..6db5700 100644
--- a/censusgeocode/__main__.py
+++ b/src/censusgeocode/__main__.py
@@ -1,4 +1,3 @@
-#!/usr/bin/env python3
 # Copyright (C) 2015-7 Neil Freeman
 
 # This program is free software: you can redistribute it and/or modify
@@ -14,8 +13,6 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 """Command-line interface for censusgeocode"""
-from __future__ import print_function
-
 import argparse
 import csv
 import io
@@ -77,6 +74,7 @@ def main():
 
         except IndexError:
             print("Address not found: {}".format(args.address), file=sys.stderr)
+            sys.exit(1)
 
     elif args.csv:
         if args.csv == "-":
@@ -97,7 +95,8 @@ def main():
         writer.writerows(result)
 
     else:
-        print("Address or csv file required")
+        print("Address or csv file required", file=sys.stderr)
+        sys.exit(1)
 
 
 if __name__ == "__main__":
diff --git a/censusgeocode/censusgeocode.py b/src/censusgeocode/censusgeocode.py
similarity index 88%
rename from censusgeocode/censusgeocode.py
rename to src/censusgeocode/censusgeocode.py
index ad70215..3db60a3 100644
--- a/censusgeocode/censusgeocode.py
+++ b/src/censusgeocode/censusgeocode.py
@@ -1,5 +1,3 @@
-#!/usr/bin/env python3
-
 # Copyright (C) 2015-9 Neil Freeman
 
 # This program is free software: you can redistribute it and/or modify
@@ -14,14 +12,14 @@
 
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
 """
 Census Geocoder wrapper
-see http://geocoding.geo.census.gov/geocoder/Geocoding_Services_API.pdf
-Accepts either named `lat` and `lng` or x and y inputs.
+For details on the API, see:
+http://geocoding.geo.census.gov/geocoder/Geocoding_Services_API.pdf
 """
 import csv
 import io
+import warnings
 
 import requests
 from requests.exceptions import RequestException
@@ -120,13 +118,13 @@ def coordinates(self, x, y, **kwargs):
 
         return self._fetch("coordinates", fields, **kwargs)
 
-    def address(self, street, city=None, state=None, zipcode=None, **kwargs):
+    def address(self, street, city=None, state=None, **kwargs):
         """Geocode an address."""
         fields = {
             "street": street,
             "city": city,
             "state": state,
-            "zip": zipcode,
+            "zip": kwargs.get('zip') or kwargs.get('zipcode'),
         }
 
         return self._fetch("address", fields, **kwargs)
@@ -143,24 +141,24 @@ def onelineaddress(self, address, **kwargs):
 
     def set_benchmark(self, benchmark):
         """Set the Census Geocoding API benchmark the class will use.
-        See https://geocoding.geo.census.gov/geocoder/vintages?form for more."""
+        See: https://geocoding.geo.census.gov/geocoder/vintages?form"""
         self._benchmark = benchmark
 
     @property
     def benchmark(self):
         """Give the Census Geocoding API benchmark the class is using.
-        See https://geocoding.geo.census.gov/geocoder/benchmarks for more."""
+        See: https://geocoding.geo.census.gov/geocoder/benchmarks"""
         return getattr(self, "_benchmark")
 
     def set_vintage(self, vintage):
         """Set the Census Geocoding API vintage the class will use.
-        See https://geocoding.geo.census.gov/geocoder/vintages?form for more."""
+        See: https://geocoding.geo.census.gov/geocoder/vintages?form"""
         self._vintage = vintage
 
     @property
     def vintage(self):
         """Give the Census Geocoding API vintage the class is using.
-        See https://geocoding.geo.census.gov/geocoder/vintages?form for more."""
+        See: https://geocoding.geo.census.gov/geocoder/vintages?form"""
         return getattr(self, "_vintage")
 
     def _parse_batch_result(self, data, returntype):
@@ -193,13 +191,15 @@ def _post_batch(self, data=None, f=None, **kwargs):
         returntype = kwargs.get("returntype", "geographies")
         url = self._geturl("addressbatch", returntype)
 
-        if data is not None:
+        if data:
             # For Python 3, compile data into a StringIO
             f = io.StringIO()
             writer = csv.DictWriter(f, fieldnames=["id", "street", "city", "state", "zip"])
-            for i, row in enumerate(data):
+            for i, row in enumerate(data, 1):
                 row.setdefault("id", i)
                 writer.writerow(row)
+                if i == 10001:
+                    warnings.warn("Sending more than 10,000 records, the upper limit for the Census Geocoder. Request will likely fail")
 
             f.seek(0)
 
@@ -229,9 +229,13 @@ def _post_batch(self, data=None, f=None, **kwargs):
     def addressbatch(self, data, **kwargs):
         """
         Send either a CSV file or data to the addressbatch API.
-        According to the Census, "there is currently an upper limit of 1000 records per batch file."
-        If a file, must have no header and fields id,street,city,state,zip
-        If data, should be a list of dicts with the above fields (although ID is optional)
+
+        According to the Census, "there is currently an upper limit of 10,000 records per batch file."
+
+        If a file, can either be a file-like with a `read()` method, or a `str` that's a path to the
+        file. Either way, it must have no header and have fields id,street,city,state,zip
+
+        If data, should be an iterable of dicts with the above fields (although ID is optional).
         """
         # Does data quack like a file handle?
         if hasattr(data, "read"):
@@ -242,7 +246,7 @@ def addressbatch(self, data, **kwargs):
             with open(data, "rb") as f:
                 return self._post_batch(f=f, **kwargs)
 
-        # Otherwise, assume a list of dicts
+        # Otherwise, assume an iterable of dicts
         return self._post_batch(data=data, **kwargs)
 
 
@@ -250,8 +254,6 @@ class GeographyResult(dict):
 
     """Wrapper for geography objects returned by the Census Geocoding API"""
 
-    _coordkeys = ("CENTLON", "CENTLAT", "INTPTLON", "INTPTLAT")
-
     def __init__(self, data):
         self.input = data["result"].get("input", {})
         super().__init__(data["result"]["geographies"])
diff --git a/tox.ini b/tox.ini
deleted file mode 100644
index 7a5c148..0000000
--- a/tox.ini
+++ /dev/null
@@ -1,13 +0,0 @@
-# This file is part of censusgeocode.
-# https://github.com/fitnr/censusgeocode
-
-# Licensed under the General Public License (version 3)
-# http://opensource.org/licenses/LGPL-3.0
-# Copyright (c) 2015-7, Neil Freeman <contact@fakeisthenewreal.org>
-
-[tox]
-envlist = py36, py37, py38, py39
-
-[testenv]
-deps = vcrpy>=1.11.1,<1.12
-commands = python -m unittest


Test patch:
diff --git a/tests/__init__.py b/tests/__init__.py
index f85a293..4e2409f 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -1,4 +1,3 @@
-#!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
 # This file is part of censusgeocode.
diff --git a/tests/test_censusgeocode.py b/tests/test_censusgeocode.py
index b05efcd..51cee20 100644
--- a/tests/test_censusgeocode.py
+++ b/tests/test_censusgeocode.py
@@ -1,6 +1,5 @@
-#!/usr/bin/env python
 # -*- coding: utf-8 -*-
-
+"""Tests for censusgeocode"""
 # This file is part of censusgeocode.
 # https://github.com/fitnr/censusgeocode
 
@@ -10,6 +9,8 @@
 
 import unittest
 import vcr
+import warnings
+
 from censusgeocode import CensusGeocode
 from censusgeocode.censusgeocode import AddressResult, GeographyResult
 
@@ -39,11 +40,17 @@ def test_url(self):
         assert r == 'https://geocoding.geo.census.gov/geocoder/geographies/coordinates'
 
     @vcr.use_cassette('tests/fixtures/address-geographies.yaml')
-    def test_address(self):
+    def test_address_zipcode(self):
         results = self.cg.address('1600 Pennsylvania Avenue NW', city='Washington', state='DC', zipcode='20500')
         assert results[0]
         assert results[0]['geographies']['Counties'][0]['BASENAME'] == 'District of Columbia'
 
+    @vcr.use_cassette('tests/fixtures/address-geographies.yaml')
+    def test_address_zip(self):
+        results = self.cg.address('1600 Pennsylvania Avenue NW', city='Washington', state='DC', zip='20500')
+        assert results[0]
+        assert results[0]['geographies']['Counties'][0]['BASENAME'] == 'District of Columbia'
+
     @vcr.use_cassette('tests/fixtures/onelineaddress.yaml')
     def test_onelineaddress(self):
         results = self.cg.onelineaddress('1600 Pennsylvania Avenue NW, Washington, DC, 20500', layers='all')
@@ -66,6 +73,7 @@ def test_address_return_type(self):
 
     @vcr.use_cassette('tests/fixtures/test_benchmark_vintage.yaml')
     def test_benchmark_vintage(self):
+        """Initializing CensuGeocode with benchmark and vintage keywords works"""
         bmark, vint = 'Public_AR_Census2020', 'Census2020_Current'
 
         cg = CensusGeocode(benchmark=bmark, vintage=vint)
@@ -77,6 +85,7 @@ def test_benchmark_vintage(self):
 
     @vcr.use_cassette('tests/fixtures/address-batch.yaml')
     def test_addressbatch(self):
+        """batch() function works"""
         result = self.cg.addressbatch('tests/fixtures/batch.csv', returntype='locations')
         assert isinstance(result, list)
         resultdict = {int(r['id']): r for r in result}
@@ -89,3 +98,12 @@ def test_addressbatch(self):
         assert resultdict[3]['tigerlineid'] == '59653655'
         assert resultdict[3]['statefp'] == '36'
         assert resultdict[2]['match'] is False
+
+    def test_warning10k(self):
+        """Sending more than 10,000 records to batch raises a warning"""
+        warnings.simplefilter("error")
+        data = ({} for _ in range(10001))
+        result = []
+        with self.assertRaises(UserWarning, msg="Get a warning when sending more than 10k rows to batch()"):
+            result = self.cg.addressbatch(data)
+        self.assertEqual(result, [], "Result is empty")