zipcodes 1.2.0__tar.gz → 2.0.1__tar.gz

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.
@@ -0,0 +1,240 @@
1
+ # This file is automatically @generated by Cargo.
2
+ # It is not intended for manual editing.
3
+ version = 3
4
+
5
+ [[package]]
6
+ name = "bzip2"
7
+ version = "0.6.1"
8
+ source = "registry+https://github.com/rust-lang/crates.io-index"
9
+ checksum = "f3a53fac24f34a81bc9954b5d6cfce0c21e18ec6959f44f56e8e90e4bb7c346c"
10
+ dependencies = [
11
+ "libbz2-rs-sys",
12
+ ]
13
+
14
+ [[package]]
15
+ name = "heck"
16
+ version = "0.5.0"
17
+ source = "registry+https://github.com/rust-lang/crates.io-index"
18
+ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
19
+
20
+ [[package]]
21
+ name = "itoa"
22
+ version = "1.0.18"
23
+ source = "registry+https://github.com/rust-lang/crates.io-index"
24
+ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
25
+
26
+ [[package]]
27
+ name = "libbz2-rs-sys"
28
+ version = "0.2.5"
29
+ source = "registry+https://github.com/rust-lang/crates.io-index"
30
+ checksum = "34b357333733e8260735ba5894eb928c02ecc69c78715f01a8019e7fa7f2db4c"
31
+
32
+ [[package]]
33
+ name = "libc"
34
+ version = "0.2.186"
35
+ source = "registry+https://github.com/rust-lang/crates.io-index"
36
+ checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
37
+
38
+ [[package]]
39
+ name = "memchr"
40
+ version = "2.8.2"
41
+ source = "registry+https://github.com/rust-lang/crates.io-index"
42
+ checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4"
43
+
44
+ [[package]]
45
+ name = "once_cell"
46
+ version = "1.21.4"
47
+ source = "registry+https://github.com/rust-lang/crates.io-index"
48
+ checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
49
+
50
+ [[package]]
51
+ name = "portable-atomic"
52
+ version = "1.13.1"
53
+ source = "registry+https://github.com/rust-lang/crates.io-index"
54
+ checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
55
+
56
+ [[package]]
57
+ name = "proc-macro2"
58
+ version = "1.0.106"
59
+ source = "registry+https://github.com/rust-lang/crates.io-index"
60
+ checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
61
+ dependencies = [
62
+ "unicode-ident",
63
+ ]
64
+
65
+ [[package]]
66
+ name = "pyo3"
67
+ version = "0.29.0"
68
+ source = "registry+https://github.com/rust-lang/crates.io-index"
69
+ checksum = "cd274650b21d4bfc26a0a47587962c1edb425f69287324355cd040c3ea66071c"
70
+ dependencies = [
71
+ "libc",
72
+ "once_cell",
73
+ "portable-atomic",
74
+ "pyo3-build-config",
75
+ "pyo3-ffi",
76
+ "pyo3-macros",
77
+ ]
78
+
79
+ [[package]]
80
+ name = "pyo3-build-config"
81
+ version = "0.29.0"
82
+ source = "registry+https://github.com/rust-lang/crates.io-index"
83
+ checksum = "c5e2a7d2f0d013342f295c048ad19237add5154a55b1c5a254c0ec93d4109078"
84
+ dependencies = [
85
+ "target-lexicon",
86
+ ]
87
+
88
+ [[package]]
89
+ name = "pyo3-ffi"
90
+ version = "0.29.0"
91
+ source = "registry+https://github.com/rust-lang/crates.io-index"
92
+ checksum = "ca85c467da1bbc8d866eea5deff9cf29ea5f7785054a17da36e65bda9c05845b"
93
+ dependencies = [
94
+ "libc",
95
+ "pyo3-build-config",
96
+ ]
97
+
98
+ [[package]]
99
+ name = "pyo3-macros"
100
+ version = "0.29.0"
101
+ source = "registry+https://github.com/rust-lang/crates.io-index"
102
+ checksum = "9ac53762fd065daa3194dd09337a38bd793a188100fd1a9304c4ab312d901771"
103
+ dependencies = [
104
+ "proc-macro2",
105
+ "pyo3-macros-backend",
106
+ "quote",
107
+ "syn",
108
+ ]
109
+
110
+ [[package]]
111
+ name = "pyo3-macros-backend"
112
+ version = "0.29.0"
113
+ source = "registry+https://github.com/rust-lang/crates.io-index"
114
+ checksum = "4ca3a1557399783172dc5bf39cfca835157732532cba56b71d2292161e53b362"
115
+ dependencies = [
116
+ "heck",
117
+ "proc-macro2",
118
+ "quote",
119
+ "syn",
120
+ ]
121
+
122
+ [[package]]
123
+ name = "quote"
124
+ version = "1.0.45"
125
+ source = "registry+https://github.com/rust-lang/crates.io-index"
126
+ checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
127
+ dependencies = [
128
+ "proc-macro2",
129
+ ]
130
+
131
+ [[package]]
132
+ name = "serde"
133
+ version = "1.0.228"
134
+ source = "registry+https://github.com/rust-lang/crates.io-index"
135
+ checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
136
+ dependencies = [
137
+ "serde_core",
138
+ "serde_derive",
139
+ ]
140
+
141
+ [[package]]
142
+ name = "serde_core"
143
+ version = "1.0.228"
144
+ source = "registry+https://github.com/rust-lang/crates.io-index"
145
+ checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
146
+ dependencies = [
147
+ "serde_derive",
148
+ ]
149
+
150
+ [[package]]
151
+ name = "serde_derive"
152
+ version = "1.0.228"
153
+ source = "registry+https://github.com/rust-lang/crates.io-index"
154
+ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
155
+ dependencies = [
156
+ "proc-macro2",
157
+ "quote",
158
+ "syn",
159
+ ]
160
+
161
+ [[package]]
162
+ name = "serde_json"
163
+ version = "1.0.150"
164
+ source = "registry+https://github.com/rust-lang/crates.io-index"
165
+ checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
166
+ dependencies = [
167
+ "itoa",
168
+ "memchr",
169
+ "serde",
170
+ "serde_core",
171
+ "zmij",
172
+ ]
173
+
174
+ [[package]]
175
+ name = "syn"
176
+ version = "2.0.117"
177
+ source = "registry+https://github.com/rust-lang/crates.io-index"
178
+ checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
179
+ dependencies = [
180
+ "proc-macro2",
181
+ "quote",
182
+ "unicode-ident",
183
+ ]
184
+
185
+ [[package]]
186
+ name = "target-lexicon"
187
+ version = "0.13.5"
188
+ source = "registry+https://github.com/rust-lang/crates.io-index"
189
+ checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca"
190
+
191
+ [[package]]
192
+ name = "thiserror"
193
+ version = "2.0.18"
194
+ source = "registry+https://github.com/rust-lang/crates.io-index"
195
+ checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
196
+ dependencies = [
197
+ "thiserror-impl",
198
+ ]
199
+
200
+ [[package]]
201
+ name = "thiserror-impl"
202
+ version = "2.0.18"
203
+ source = "registry+https://github.com/rust-lang/crates.io-index"
204
+ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
205
+ dependencies = [
206
+ "proc-macro2",
207
+ "quote",
208
+ "syn",
209
+ ]
210
+
211
+ [[package]]
212
+ name = "unicode-ident"
213
+ version = "1.0.24"
214
+ source = "registry+https://github.com/rust-lang/crates.io-index"
215
+ checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
216
+
217
+ [[package]]
218
+ name = "zipcodes"
219
+ version = "2.0.1"
220
+ dependencies = [
221
+ "bzip2",
222
+ "serde",
223
+ "serde_json",
224
+ "thiserror",
225
+ ]
226
+
227
+ [[package]]
228
+ name = "zipcodes-py"
229
+ version = "2.0.1"
230
+ dependencies = [
231
+ "pyo3",
232
+ "serde_json",
233
+ "zipcodes",
234
+ ]
235
+
236
+ [[package]]
237
+ name = "zmij"
238
+ version = "1.0.21"
239
+ source = "registry+https://github.com/rust-lang/crates.io-index"
240
+ checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
@@ -0,0 +1,15 @@
1
+ [workspace]
2
+ members = ["crates/zipcodes", "crates/zipcodes-py"]
3
+ resolver = "2"
4
+
5
+ [workspace.package]
6
+ version = "2.0.1"
7
+ edition = "2021"
8
+ rust-version = "1.82"
9
+ authors = ["Sean Pianka <pianka@eml.cc>"]
10
+ license = "MIT"
11
+ repository = "https://github.com/seanpianka/zipcodes"
12
+ homepage = "https://github.com/seanpianka/zipcodes"
13
+
14
+ [profile.release]
15
+ lto = true
@@ -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
+ ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/zipcodes)
27
+ [![PyPI](https://img.shields.io/pypi/v/zipcodes)](https://pypi.org/project/zipcodes/)
28
+ [![crates.io](https://img.shields.io/crates/v/zipcodes)](https://crates.io/crates/zipcodes)
29
+ [![Downloads](https://static.pepy.tech/badge/zipcodes/month)](https://pepy.tech/project/zipcodes)
30
+ [![Contributors](https://img.shields.io/github/contributors/seanpianka/zipcodes.svg)](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
+