zipcodes 2.0.1__cp39-abi3-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- zipcodes/__init__.py +174 -0
- zipcodes/_zipcodes.pyd +0 -0
- zipcodes/_zipcodes.pyi +14 -0
- zipcodes/py.typed +0 -0
- zipcodes-2.0.1.dist-info/METADATA +299 -0
- zipcodes-2.0.1.dist-info/RECORD +9 -0
- zipcodes-2.0.1.dist-info/WHEEL +4 -0
- zipcodes-2.0.1.dist-info/licenses/LICENSE.txt +20 -0
- zipcodes-2.0.1.dist-info/sboms/zipcodes-py.cyclonedx.json +1004 -0
zipcodes/__init__.py
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"""
|
|
2
|
+
zipcodes
|
|
3
|
+
~~~~~~~~
|
|
4
|
+
|
|
5
|
+
No-SQLite U.S. zipcode validation Python package, ready for use in AWS Lambda
|
|
6
|
+
|
|
7
|
+
:author: Sean Pianka
|
|
8
|
+
:github: @seanpianka
|
|
9
|
+
|
|
10
|
+
The full-database scans run in the compiled Rust extension
|
|
11
|
+
(``zipcodes._zipcodes``); this module preserves the exact 1.x behavior for
|
|
12
|
+
argument validation, exceptions, and the ``zips=`` chaining lists.
|
|
13
|
+
"""
|
|
14
|
+
import re
|
|
15
|
+
import warnings
|
|
16
|
+
from math import asin, cos, radians, sin, sqrt
|
|
17
|
+
|
|
18
|
+
from zipcodes import _zipcodes
|
|
19
|
+
|
|
20
|
+
__author__ = "Sean Pianka"
|
|
21
|
+
__email__ = "pianka@eml.cc"
|
|
22
|
+
__license__ = "MIT"
|
|
23
|
+
__version__ = _zipcodes.__version__
|
|
24
|
+
|
|
25
|
+
_digits = re.compile(r"[^\d\-]")
|
|
26
|
+
_valid_zipcode_length = 5
|
|
27
|
+
|
|
28
|
+
_zips_cache = None
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _load_zips():
|
|
32
|
+
global _zips_cache
|
|
33
|
+
if _zips_cache is None:
|
|
34
|
+
_zips_cache = _zipcodes.list_all()
|
|
35
|
+
return _zips_cache
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def __getattr__(name):
|
|
39
|
+
# `_zips` was a module-level list in 1.x; keep it importable, but
|
|
40
|
+
# materialize the 42k dicts lazily instead of at import time.
|
|
41
|
+
if name == "_zips":
|
|
42
|
+
return _load_zips()
|
|
43
|
+
raise AttributeError("module {!r} has no attribute {!r}".format(__name__, name))
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _clean_zipcode(fn):
|
|
47
|
+
def decorator(zipcode, *args, **kwargs):
|
|
48
|
+
if not zipcode or not isinstance(zipcode, str):
|
|
49
|
+
raise TypeError("Invalid type, zipcode must be a string.")
|
|
50
|
+
|
|
51
|
+
return fn(
|
|
52
|
+
_clean(zipcode, min(len(zipcode), _valid_zipcode_length)), *args, **kwargs
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
return decorator
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@_clean_zipcode
|
|
59
|
+
def matching(zipcode, zips=None):
|
|
60
|
+
"""Retrieve zipcode dict for provided zipcode"""
|
|
61
|
+
if zips is None:
|
|
62
|
+
return _zipcodes.matching(zipcode)
|
|
63
|
+
return [z for z in zips if z["zip_code"] == zipcode]
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@_clean_zipcode
|
|
67
|
+
def is_valid(zipcode):
|
|
68
|
+
warnings.warn("is_valid is deprecated; use is_real", DeprecationWarning, stacklevel=2)
|
|
69
|
+
return is_real(zipcode)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@_clean_zipcode
|
|
73
|
+
def is_real(zipcode):
|
|
74
|
+
"""Determine whether a given zip or zip+4 zipcode is real."""
|
|
75
|
+
return _zipcodes.is_real(zipcode)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@_clean_zipcode
|
|
79
|
+
def similar_to(partial_zipcode, zips=None):
|
|
80
|
+
"""List of zipcode dicts where zipcode prefix matches `partial_zipcode`"""
|
|
81
|
+
if zips is None:
|
|
82
|
+
return _zipcodes.similar_to(partial_zipcode)
|
|
83
|
+
return [z for z in zips if z["zip_code"].startswith(partial_zipcode)]
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@_clean_zipcode
|
|
87
|
+
def contains(partial_zipcode, zips=None):
|
|
88
|
+
"""List of zipcode dicts where zipcode contains `partial_zipcode` fragment"""
|
|
89
|
+
if zips is None:
|
|
90
|
+
return _zipcodes.contains(partial_zipcode)
|
|
91
|
+
return [z for z in zips if partial_zipcode in z["zip_code"]]
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def filter_by_state(state, zips=None):
|
|
95
|
+
return filter_by(zips, state=state)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def filter_by_city(city, zips=None):
|
|
99
|
+
return filter_by(zips, city=city)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def filter_by_county(county, zips=None):
|
|
103
|
+
return filter_by(zips, county=county)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def filter_by_timezone(timezone, zips=None):
|
|
107
|
+
return filter_by(zips, timezone=timezone)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def filter_by_zip_code_type(zip_code_type, zips=None):
|
|
111
|
+
return filter_by(zips, zip_code_type=zip_code_type)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def haversine(lon1, lat1, lon2, lat2):
|
|
115
|
+
"""
|
|
116
|
+
Calculate the great circle distance in miles between two points
|
|
117
|
+
on the earth (specified in decimal degrees)
|
|
118
|
+
"""
|
|
119
|
+
lon1, lat1, lon2, lat2 = map(radians, [lon1, lat1, lon2, lat2])
|
|
120
|
+
|
|
121
|
+
dlon = lon2 - lon1
|
|
122
|
+
dlat = lat2 - lat1
|
|
123
|
+
a = sin(dlat / 2) ** 2 + cos(lat1) * cos(lat2) * sin(dlon / 2) ** 2
|
|
124
|
+
c = 2 * asin(sqrt(a))
|
|
125
|
+
r = 3956 # Radius of earth in miles. Use 6371 for kilometers.
|
|
126
|
+
return c * r
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def filter_by_coordinates(lat, long, radius_in_miles=10, zips=None):
|
|
130
|
+
"""List of zipcode dicts within `radius_in_miles` of (`lat`, `long`)."""
|
|
131
|
+
if zips is None:
|
|
132
|
+
return _zipcodes.filter_by_coordinates(lat, long, radius_in_miles)
|
|
133
|
+
return [
|
|
134
|
+
z
|
|
135
|
+
for z in zips
|
|
136
|
+
if haversine(float(z["long"]), float(z["lat"]), long, lat) <= radius_in_miles
|
|
137
|
+
]
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def filter_by(zips=None, **filters):
|
|
141
|
+
"""Use `kwargs` to select for desired attributes from list of zipcode dicts"""
|
|
142
|
+
if zips is None:
|
|
143
|
+
return _zipcodes.filter_by(**filters)
|
|
144
|
+
return [
|
|
145
|
+
z
|
|
146
|
+
for z in zips
|
|
147
|
+
if all(key in z and z[key] == value for key, value in filters.items())
|
|
148
|
+
]
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def list_all(zips=None):
|
|
152
|
+
"""Return a list containing all zip-code objects."""
|
|
153
|
+
if zips is None:
|
|
154
|
+
return _load_zips()
|
|
155
|
+
return zips
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def _contains_nondigits(s):
|
|
159
|
+
return bool(_digits.search(s))
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def _clean(zipcode, valid_length=_valid_zipcode_length):
|
|
163
|
+
"""Assumes zipcode is of type `str`"""
|
|
164
|
+
zipcode = zipcode.split("-")[0] # Convert #####-#### to #####
|
|
165
|
+
|
|
166
|
+
if len(zipcode) != valid_length:
|
|
167
|
+
raise ValueError(
|
|
168
|
+
'Invalid format, zipcode must be of the format: "#####" or "#####-####"'
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
if _contains_nondigits(zipcode):
|
|
172
|
+
raise ValueError('Invalid characters, zipcode may only contain digits and "-".')
|
|
173
|
+
|
|
174
|
+
return zipcode
|
zipcodes/_zipcodes.pyd
ADDED
|
Binary file
|
zipcodes/_zipcodes.pyi
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from typing import Any, Dict, List
|
|
2
|
+
|
|
3
|
+
__version__: str
|
|
4
|
+
|
|
5
|
+
def matching(zipcode: str) -> List[Dict[str, Any]]: ...
|
|
6
|
+
def is_real(zipcode: str) -> bool: ...
|
|
7
|
+
def similar_to(prefix: str) -> List[Dict[str, Any]]: ...
|
|
8
|
+
def contains(fragment: str) -> List[Dict[str, Any]]: ...
|
|
9
|
+
def filter_by(**filters: Any) -> List[Dict[str, Any]]: ...
|
|
10
|
+
def filter_by_coordinates(
|
|
11
|
+
lat: float, long: float, radius_in_miles: float
|
|
12
|
+
) -> List[Dict[str, Any]]: ...
|
|
13
|
+
def haversine(lon1: float, lat1: float, lon2: float, lat2: float) -> float: ...
|
|
14
|
+
def list_all() -> List[Dict[str, Any]]: ...
|
zipcodes/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: zipcodes
|
|
3
|
+
Version: 2.0.1
|
|
4
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
5
|
+
Classifier: Intended Audience :: Developers
|
|
6
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
7
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
12
|
+
Classifier: Programming Language :: Rust
|
|
13
|
+
License-File: LICENSE.txt
|
|
14
|
+
Summary: Query U.S. state zipcodes without SQLite.
|
|
15
|
+
Keywords: zipcode,zip,code,us,state,query,filter,validate,sqlite
|
|
16
|
+
Home-Page: https://github.com/seanpianka/zipcodes
|
|
17
|
+
Author-email: Sean Pianka <pianka@eml.cc>
|
|
18
|
+
License-Expression: MIT
|
|
19
|
+
Requires-Python: >=3.9
|
|
20
|
+
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
21
|
+
Project-URL: Homepage, https://github.com/seanpianka/zipcodes
|
|
22
|
+
Project-URL: Issues, https://github.com/seanpianka/zipcodes/issues
|
|
23
|
+
|
|
24
|
+
# Zipcodes
|
|
25
|
+
|
|
26
|
+

|
|
27
|
+
[](https://pypi.org/project/zipcodes/)
|
|
28
|
+
[](https://crates.io/crates/zipcodes)
|
|
29
|
+
[](https://pepy.tech/project/zipcodes)
|
|
30
|
+
[](https://github.com/seanpianka/zipcodes/graphs/contributors)
|
|
31
|
+
|
|
32
|
+
Zipcodes is a simple library for querying U.S. zipcodes. No SQLite, no
|
|
33
|
+
network, no runtime data files — the full dataset is embedded in the package.
|
|
34
|
+
|
|
35
|
+
Since 2.0, the library is implemented in Rust and published from a single
|
|
36
|
+
codebase as both the [`zipcodes` Python package](https://pypi.org/project/zipcodes/)
|
|
37
|
+
and the [`zipcodes` Rust crate](https://crates.io/crates/zipcodes). The Python
|
|
38
|
+
API is a drop-in replacement for 1.x — same functions, same dicts, same
|
|
39
|
+
exceptions — just faster:
|
|
40
|
+
|
|
41
|
+
| Operation | 1.x (pure Python) | 2.0 (Rust) |
|
|
42
|
+
|---|---|---|
|
|
43
|
+
| `import zipcodes` | ~330 ms (loads dataset) | ~5 ms (dataset loads lazily on first query, ~200 ms) |
|
|
44
|
+
| `is_real("06903")` | ~4.2 ms | ~0.03 ms |
|
|
45
|
+
| `matching("77429")` | ~4.2 ms | ~0.3 ms |
|
|
46
|
+
| `similar_to("1018")` | ~7.2 ms | ~0.3 ms |
|
|
47
|
+
| `filter_by(state="TX")` | ~9.7 ms | ~3.7 ms |
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
>>> import zipcodes
|
|
51
|
+
>>> assert zipcodes.is_real('77429')
|
|
52
|
+
>>> assert len(zipcodes.similar_to('7742')) != 0
|
|
53
|
+
>>> exact_zip = zipcodes.matching('77429')[0]
|
|
54
|
+
>>> filtered_zips = zipcodes.filter_by(city="Cypress", state="TX")
|
|
55
|
+
>>> assert exact_zip in filtered_zips
|
|
56
|
+
>>> pprint.pprint(exact_zip)
|
|
57
|
+
{'acceptable_cities': [],
|
|
58
|
+
'active': True,
|
|
59
|
+
'area_codes': ['281', '346', '713', '832'],
|
|
60
|
+
'city': 'Cypress',
|
|
61
|
+
'country': 'US',
|
|
62
|
+
'county': 'Harris County',
|
|
63
|
+
'lat': '29.9766',
|
|
64
|
+
'long': '-95.6358',
|
|
65
|
+
'state': 'TX',
|
|
66
|
+
'timezone': 'America/Chicago',
|
|
67
|
+
'unacceptable_cities': [],
|
|
68
|
+
'world_region': 'NA',
|
|
69
|
+
'zip_code': '77429',
|
|
70
|
+
'zip_code_type': 'STANDARD'}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
The zipcode data is refreshed automatically every month — see
|
|
74
|
+
[Zipcode Data](#zipcode-data).
|
|
75
|
+
|
|
76
|
+
## Installation
|
|
77
|
+
|
|
78
|
+
### Python
|
|
79
|
+
|
|
80
|
+
```console
|
|
81
|
+
$ python -m pip install zipcodes
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Zipcodes 2.x supports Python 3.9+ and ships prebuilt wheels for Linux
|
|
85
|
+
(x86_64, aarch64, musl), macOS, and Windows. Installing from the source
|
|
86
|
+
distribution requires a Rust toolchain. Python 2.6+/3.2+ users are
|
|
87
|
+
automatically served the pure-Python 1.3.0 release by pip.
|
|
88
|
+
|
|
89
|
+
### Rust
|
|
90
|
+
|
|
91
|
+
```console
|
|
92
|
+
$ cargo add zipcodes
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### New in 2.0
|
|
96
|
+
|
|
97
|
+
- Implemented in Rust; the dataset is compiled into the extension module and
|
|
98
|
+
decompressed lazily on first query, so `import zipcodes` is effectively free.
|
|
99
|
+
- New functions: `contains`, `filter_by_state`, `filter_by_city`,
|
|
100
|
+
`filter_by_county`, `filter_by_timezone`, `filter_by_zip_code_type`,
|
|
101
|
+
`filter_by_coordinates`, and `haversine`.
|
|
102
|
+
- `is_valid` now actually emits its `DeprecationWarning` (in 1.x it raised
|
|
103
|
+
`AttributeError`); use `is_real`.
|
|
104
|
+
- PyInstaller users no longer need `--add-data` for `zips.json.bz2` — there is
|
|
105
|
+
no data file anymore.
|
|
106
|
+
- Behavioral notes for upgraders: query results are fresh dicts (mutating a
|
|
107
|
+
result no longer mutates the shared database list), and
|
|
108
|
+
`filter_by(active=1)` no longer matches `active=True` (pass a bool).
|
|
109
|
+
|
|
110
|
+
## Zipcode Data
|
|
111
|
+
|
|
112
|
+
The embedded dataset (`crates/zipcodes/src/zips.json.bz2`) is assembled from
|
|
113
|
+
three sources:
|
|
114
|
+
|
|
115
|
+
- **[unitedstateszipcodes.org](https://www.unitedstateszipcodes.org)** — the
|
|
116
|
+
rich base data (city aliases, zip type, area codes, county, timezone),
|
|
117
|
+
committed in-repo as `scripts/data/zip_code_database.csv`. Its bot
|
|
118
|
+
protection prevents automated downloads, so this file is refreshed manually
|
|
119
|
+
on occasion.
|
|
120
|
+
- **[GeoNames](https://www.geonames.org/)** (`download.geonames.org/export/zip/US.zip`) —
|
|
121
|
+
GPS coordinates, fetched fresh on every update. Licensed
|
|
122
|
+
[CC BY 4.0](https://creativecommons.org/licenses/by/4.0/).
|
|
123
|
+
- **USPS ZIP Locale Detail** ([postalpro.usps.com](https://postalpro.usps.com/ZIP_Locale_Detail)) —
|
|
124
|
+
the authoritative list of active delivery ZIPs, fetched fresh on every
|
|
125
|
+
update and used to add zipcodes missing from the base CSV
|
|
126
|
+
([#23](https://github.com/seanpianka/Zipcodes/issues/23)). Public domain.
|
|
127
|
+
|
|
128
|
+
A GitHub Actions workflow ([`update-data.yml`](.github/workflows/update-data.yml))
|
|
129
|
+
rebuilds the dataset on the 1st of every month
|
|
130
|
+
([#7](https://github.com/seanpianka/Zipcodes/issues/7)). If anything changed,
|
|
131
|
+
it opens a pull request with a summary of added/removed/modified records;
|
|
132
|
+
merging that PR tags a patch release, which publishes to PyPI and crates.io
|
|
133
|
+
automatically.
|
|
134
|
+
|
|
135
|
+
To rebuild the dataset manually:
|
|
136
|
+
|
|
137
|
+
```shell script
|
|
138
|
+
$ pip install xlrd
|
|
139
|
+
$ curl -fsSL https://download.geonames.org/export/zip/US.zip -o /tmp/geonames_us.zip
|
|
140
|
+
$ # download the .xls linked from https://postalpro.usps.com/ZIP_Locale_Detail
|
|
141
|
+
$ python scripts/update_zipcode_dataset.py \
|
|
142
|
+
--base scripts/data/zip_code_database.csv \
|
|
143
|
+
--gps scripts/data/zip-codes-database-FREE.csv \
|
|
144
|
+
--geonames-zip /tmp/geonames_us.zip \
|
|
145
|
+
--usps-xls /tmp/usps_zip_locale.xls \
|
|
146
|
+
--output-bz2 crates/zipcodes/src/zips.json.bz2 \
|
|
147
|
+
--summary-output /tmp/change_summary.json
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Tests
|
|
151
|
+
|
|
152
|
+
The tests are defined in a declarative, table-based format that generates test
|
|
153
|
+
cases.
|
|
154
|
+
|
|
155
|
+
```shell script
|
|
156
|
+
$ cargo test # Rust unit tests
|
|
157
|
+
$ python tests/__init__.py # Python suite (or: pytest tests/)
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Examples
|
|
161
|
+
|
|
162
|
+
```python
|
|
163
|
+
>>> from pprint import pprint
|
|
164
|
+
>>> import zipcodes
|
|
165
|
+
|
|
166
|
+
>>> # Simple zip-code matching.
|
|
167
|
+
>>> pprint(zipcodes.matching('77429'))
|
|
168
|
+
[{'acceptable_cities': [],
|
|
169
|
+
'active': True,
|
|
170
|
+
'area_codes': ['281', '346', '713', '832'],
|
|
171
|
+
'city': 'Cypress',
|
|
172
|
+
'country': 'US',
|
|
173
|
+
'county': 'Harris County',
|
|
174
|
+
'lat': '29.9766',
|
|
175
|
+
'long': '-95.6358',
|
|
176
|
+
'state': 'TX',
|
|
177
|
+
'timezone': 'America/Chicago',
|
|
178
|
+
'unacceptable_cities': [],
|
|
179
|
+
'world_region': 'NA',
|
|
180
|
+
'zip_code': '77429',
|
|
181
|
+
'zip_code_type': 'STANDARD'}]
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
>>> # Handles Zip+4 zip-codes nicely. :)
|
|
185
|
+
>>> pprint(zipcodes.matching('77429-1145'))
|
|
186
|
+
[{'acceptable_cities': [],
|
|
187
|
+
'active': True,
|
|
188
|
+
'area_codes': ['281', '346', '713', '832'],
|
|
189
|
+
'city': 'Cypress',
|
|
190
|
+
'country': 'US',
|
|
191
|
+
'county': 'Harris County',
|
|
192
|
+
'lat': '29.9766',
|
|
193
|
+
'long': '-95.6358',
|
|
194
|
+
'state': 'TX',
|
|
195
|
+
'timezone': 'America/Chicago',
|
|
196
|
+
'unacceptable_cities': [],
|
|
197
|
+
'world_region': 'NA',
|
|
198
|
+
'zip_code': '77429',
|
|
199
|
+
'zip_code_type': 'STANDARD'}]
|
|
200
|
+
|
|
201
|
+
>>> # Will try to handle invalid zip-codes gracefully...
|
|
202
|
+
>>> print(zipcodes.matching('06463'))
|
|
203
|
+
[]
|
|
204
|
+
|
|
205
|
+
>>> # Until it cannot.
|
|
206
|
+
>>> zipcodes.matching('0646a')
|
|
207
|
+
Traceback (most recent call last):
|
|
208
|
+
...
|
|
209
|
+
ValueError: Invalid characters, zipcode may only contain digits and "-".
|
|
210
|
+
|
|
211
|
+
>>> zipcodes.matching('064690')
|
|
212
|
+
Traceback (most recent call last):
|
|
213
|
+
...
|
|
214
|
+
ValueError: Invalid format, zipcode must be of the format: "#####" or "#####-####"
|
|
215
|
+
|
|
216
|
+
>>> zipcodes.matching(None)
|
|
217
|
+
Traceback (most recent call last):
|
|
218
|
+
...
|
|
219
|
+
TypeError: Invalid type, zipcode must be a string.
|
|
220
|
+
|
|
221
|
+
>>> # Whether the zip-code exists within the database.
|
|
222
|
+
>>> print(zipcodes.is_real('06463'))
|
|
223
|
+
False
|
|
224
|
+
|
|
225
|
+
>>> # How handy!
|
|
226
|
+
>>> print(zipcodes.is_real('06469'))
|
|
227
|
+
True
|
|
228
|
+
|
|
229
|
+
>>> # Search for zipcodes that begin with a pattern.
|
|
230
|
+
>>> pprint(zipcodes.similar_to('1018'))
|
|
231
|
+
[{'acceptable_cities': [],
|
|
232
|
+
'active': False,
|
|
233
|
+
'area_codes': ['212'],
|
|
234
|
+
'city': 'New York',
|
|
235
|
+
'country': 'US',
|
|
236
|
+
'county': 'New York County',
|
|
237
|
+
'lat': '40.71',
|
|
238
|
+
'long': '-74',
|
|
239
|
+
'state': 'NY',
|
|
240
|
+
'timezone': 'America/New_York',
|
|
241
|
+
'unacceptable_cities': ['J C Penney'],
|
|
242
|
+
'world_region': 'NA',
|
|
243
|
+
'zip_code': '10184',
|
|
244
|
+
'zip_code_type': 'UNIQUE'},
|
|
245
|
+
{'acceptable_cities': [],
|
|
246
|
+
'active': True,
|
|
247
|
+
'area_codes': ['212'],
|
|
248
|
+
'city': 'New York',
|
|
249
|
+
'country': 'US',
|
|
250
|
+
'county': 'New York County',
|
|
251
|
+
'lat': '40.7808',
|
|
252
|
+
'long': '-73.9772',
|
|
253
|
+
'state': 'NY',
|
|
254
|
+
'timezone': 'America/New_York',
|
|
255
|
+
'unacceptable_cities': ['Manhattan', 'New York City', 'NY', 'Ny City', 'Nyc'],
|
|
256
|
+
'world_region': 'NA',
|
|
257
|
+
'zip_code': '10185',
|
|
258
|
+
'zip_code_type': 'PO BOX'}]
|
|
259
|
+
|
|
260
|
+
>>> # Use filter_by to filter a list of zip-codes by specific attribute->value pairs.
|
|
261
|
+
>>> pprint(zipcodes.filter_by(city="Old Saybrook"))
|
|
262
|
+
[{'acceptable_cities': [],
|
|
263
|
+
'active': True,
|
|
264
|
+
'area_codes': ['860', '959'],
|
|
265
|
+
'city': 'Old Saybrook',
|
|
266
|
+
'country': 'US',
|
|
267
|
+
'county': 'Middlesex County',
|
|
268
|
+
'lat': '41.2913',
|
|
269
|
+
'long': '-72.385',
|
|
270
|
+
'state': 'CT',
|
|
271
|
+
'timezone': 'America/New_York',
|
|
272
|
+
'unacceptable_cities': ['Fenwick'],
|
|
273
|
+
'world_region': 'NA',
|
|
274
|
+
'zip_code': '06475',
|
|
275
|
+
'zip_code_type': 'STANDARD'}]
|
|
276
|
+
|
|
277
|
+
>>> # Arbitrary nesting of similar_to and filter_by calls, allowing for great precision while filtering.
|
|
278
|
+
>>> pprint([z['zip_code'] for z in zipcodes.similar_to('2', zips=zipcodes.filter_by(active=True, city='Windsor'))])
|
|
279
|
+
['23487', '27983', '29856']
|
|
280
|
+
|
|
281
|
+
>>> # Find zipcodes within a radius (miles) of a coordinate.
|
|
282
|
+
>>> pprint([z['zip_code'] for z in zipcodes.filter_by_coordinates(41.3015, -72.3879, 5)])
|
|
283
|
+
['06371', '06409', '06426', '06442', '06475', '06498']
|
|
284
|
+
|
|
285
|
+
>>> # Have any other ideas? Make a pull request and start contributing today!
|
|
286
|
+
>>> # Made with love by Sean Pianka
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
## Repository layout
|
|
290
|
+
|
|
291
|
+
This repository builds both packages from one Rust core:
|
|
292
|
+
|
|
293
|
+
- `crates/zipcodes` — the core library, published to
|
|
294
|
+
[crates.io](https://crates.io/crates/zipcodes).
|
|
295
|
+
- `crates/zipcodes-py` — PyO3 bindings (not published to crates.io).
|
|
296
|
+
- `python/zipcodes` — the thin Python compatibility layer; together with the
|
|
297
|
+
bindings it forms the [PyPI package](https://pypi.org/project/zipcodes/),
|
|
298
|
+
built with [maturin](https://github.com/PyO3/maturin).
|
|
299
|
+
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
zipcodes/__init__.py,sha256=SrMF4erWLz7vwWgaqpz1TGN8w6LxxLMiXtwIwZp4KWw,4999
|
|
2
|
+
zipcodes/_zipcodes.pyd,sha256=gCcs7IwV4dXwphaO4a__kiozYiEkGBaZn4DSLa3simw,1109504
|
|
3
|
+
zipcodes/_zipcodes.pyi,sha256=0ycFzEWpGs-EzUMiG-bi9tVA71G3AR-RwVh6quSiuMk,570
|
|
4
|
+
zipcodes/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
+
zipcodes-2.0.1.dist-info/METADATA,sha256=ScTWJtLprfmg-JFPeCgGsS_ASvBBhgMwW6T-p546s8I,10675
|
|
6
|
+
zipcodes-2.0.1.dist-info/WHEEL,sha256=ZiBmjEMKX1iTyJmqXZl_jTcrFdOtPkEbr7SVi1lbRvI,95
|
|
7
|
+
zipcodes-2.0.1.dist-info/licenses/LICENSE.txt,sha256=vC0EMImFh3XwWiWttQX4zGDytVodWMDmpf4Fk0-GLR4,1061
|
|
8
|
+
zipcodes-2.0.1.dist-info/sboms/zipcodes-py.cyclonedx.json,sha256=11WrzD1EqzXpTc4AWQScxQixXyfuDKs_83KD7jwR8tI,30638
|
|
9
|
+
zipcodes-2.0.1.dist-info/RECORD,,
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
The MIT License
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
in the Software without restriction, including without limitation the rights
|
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
furnished to do so, subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
|
11
|
+
all copies or substantial portions of the Software.
|
|
12
|
+
|
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
19
|
+
THE SOFTWARE.
|
|
20
|
+
|