Skip to main content

gunsnprices

PyPI Python Tests License

Parse firearm and ammunition product titles into structured data: brand, model, caliber, magazine capacity, barrel length, rounds per box and cost per round.

>>> from gunsnprices import parse_title
>>> info = parse_title("Glock 19 Gen 5 MOS 9mm 4.02in 15rd Pistol PA195S203MOS")
>>> info.model.model, info.caliber.name, info.capacity, info.barrel_length
('Glock 19 Gen 5 MOS', '9×19mm Parabellum', 15, 4.02)

This is the title parser behind GunsNPrices, a price comparison site that tracks 40+ US gun and ammo stores. Store titles are messy (G19 GEN5 9MM 15RD FS, O/U 12/28 BL/WD 3", Federal 308 150gr ... 20rds), and matching the same product across stores starts with reading them reliably. The model dictionaries and the caliber list in this package are the ones the site uses to group listings, so you get the same model names you see on gunsnprices.com.

No dependencies, pure Python 3.9+, about 1 ms per title.

Install

pip install gunsnprices

A JavaScript version with the same API and the same test cases is on npm: npm install gunsnprices (source).

What it recognizes

Field Example in a title Result
Firearm or ammo ... 150gr ... 20rds kind="ammo"
Brand and model (50 brands) G19 Gen5 MOS, part number PA195S203 Glock 19 Gen 5 MOS
Calibers (293) .357 Mag / .38 Spl, 6.5 CM, 12ga, 300 BLK, 7MM RM .357 Mag, .38 S&W Special, ...
Magazine capacity 15+1, 17RD, 13rd/15rd, 10-shot 15, 17, 15, 10
Barrel length, inches 4.02in, 16 1/2", 22 BBL, O/U 12/28 4.02, 16.5, 22.0, 28.0
Rounds per box 50/Box, Box of 20, 1,000 Rounds, 20/25 50, 20, 1000, 20
Cost per round price 449.99 + 1000 Rounds 0.45

Brands with model dictionaries: Barrett, Benelli, Beretta, Bergara, Browning, Cadex, Canik, Charles Daly, Chiappa, Christensen Arms, Cimarron, CMMG, Colt, CVA, CZ, Daniel Defense, Desert Eagle, Diamondback, Fierce, FN, Glock, Great Lakes, Henry, Heritage, HK, Howa, Kel-Tec, Kimber, Kriss, Marlin, Mossberg, Remington, Retay, Rossi, Ruger, Savage, SCCY, Seekins, Shadow Systems, Sig Sauer, Smith & Wesson, Springfield, Stevens, Stoeger, Taurus, Tikka, Tristar, Walther, Weatherby, Winchester.

Usage

Everything at once

from gunsnprices import parse_title

info = parse_title("Federal American Eagle 5.56x45mm 55gr FMJ 1000 Rounds", price=449.99)
info.kind              # 'ammo'
info.caliber.name      # '5.56×45mm NATO'
info.rounds            # 1000
info.cost_per_round    # 0.45

info.to_dict()         # plain dict, ready for json.dumps

parse_title(title, brand=None, mpn="", sku="", price=None, kind=None) returns a ProductInfo:

Attribute Type Notes
kind str "firearm" or "ammo"; pass kind= to skip the guess
brand str brand of the identified model, else the brand you passed
model ModelMatch or None firearms only
caliber Caliber or None first caliber in the title
calibers list[Caliber] all calibers, in order
capacity int or None magazine capacity (for 15+1 it is 15), firearms only
barrel_length float or None inches, firearms only
rounds int or None rounds per box, ammo only
cost_per_round float or None ammo with price only

Passing brand (the manufacturer field from a feed) helps with titles that don't name the brand. Passing mpn lets it decode Glock part numbers when the title says nothing useful:

>>> from gunsnprices import identify_model
>>> identify_model("GLOCK 9MM PISTOL 15RD", brand="Glock", mpn="UA4350201")
ModelMatch(brand='Glock', family='Glock 43', model='Glock 43', generation='', configs=[], features=[], source='mpn')

Models

from gunsnprices import identify_model, list_brands

m = identify_model("Sig Sauer P365 XL 9mm 3.7\" 12+1 Optic Ready")
m.brand, m.family, m.model    # ('Sig Sauer', 'Sig Sauer P365', 'Sig Sauer P365 XL')

ModelMatch has brand, family, model (the most specific name), generation, configs (such as MOS, Compact), features (such as Threaded Barrel, FDE) and source ("title" or "mpn"). Accessories, parts, airguns and airsoft are rejected, so it returns None for "Glock 19 holster" and "Umarex Glock 19 CO2 BB pistol".

Calibers

from gunsnprices import find_calibers, normalize_caliber, list_calibers

[c.name for c in find_calibers("Henry Big Boy .44 Mag/.44 Spl 20in")]
# ['.44 Magnum', '.44 S&W Special']

normalize_caliber("308 WIN (7.62X51 NATO)")
# Caliber(id='308win', name='.308 Win', type='rifle', url='https://gunsnprices.com/ammo/rifle-ammo/308-win')

Each Caliber has a stable id, a display name, a type (handgun, rifle, rimfire, shotgun, airgun, muzzleloader) and the url of its GunsNPrices price comparison page. normalize_caliber is meant for caliber fields in product feeds; find_calibers searches free text.

Numbers

from gunsnprices import magazine_capacity, barrel_length, rounds_per_box, cost_per_round

magazine_capacity("Springfield Hellcat Pro OSP 9mm 3.7in 15rd/17rd")   # 17
barrel_length('Mossberg 590 Persuader 12/20 BL/SY 3"')                # 20.0 (3" is the chamber)
rounds_per_box("Winchester 308 150gr PowerMax Bonded 20rds")           # 20
cost_per_round(31.99, 20)                                              # 1.5995

Command line

$ gunsnprices "Ruger GP100 .357 Mag / .38 Special 4.2in 6-shot Revolver"
$ cat titles.txt | gunsnprices > parsed.jsonl
$ gunsnprices --price 18.99 "Federal 9mm 115gr FMJ 50/Box"

Accuracy

Measured on 50,202 firearm and 13,555 ammunition listings from 40+ stores on gunsnprices.com (October 2026), against the attributes stored on the site:

What Found in the title Right when found
Model (32,317 listings with a known model) 99.97% 99.8% same name as the site*
Caliber, firearms 81% 97.8%
Caliber, ammo 91% 94.9%
Magazine capacity 46% 95.6%
Barrel length 58% 94.4%
Rounds per box 80% 96.6%

"Found" is limited by the titles themselves: many stores don't put the caliber or barrel length in the title at all. Most of the "wrong" cases we checked by hand are errors in the store data, not in the parser: a 6mm Creedmoor rifle labeled 6.5 Creedmoor, a "1000 Rounds" case stored as a box of 20.

* The other 0.2% are the same model with a cleaner name: the site calls 52 listings "S&W M&P Shield Plus Plus Shield", the library says "S&W M&P Shield Plus".

Data

The model dictionaries and caliber list ship inside the package (gunsnprices/data/). The same data, with listing counts and price levels, is published as open datasets:

Limitations

  • Titles from US retailers, in English.
  • Models are covered for the 50 brands above. Other brands still get calibers and numbers.
  • Built for firearms and ammunition. Magazines, parts and accessories get calibers and numbers but no model.

License

The code is under the MIT License. The data files in gunsnprices/data/ are under CC BY 4.0: you can use them for anything, including commercial use, if you credit GunsNPrices (gunsnprices.com) with a link.

Questions, corrections and missing models: open an issue or write to app@gunsnprices.com. Current gun and ammo prices: gunsnprices.com.

Metadata

Release files for gunsnprices 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for gunsnprices 0.1.1
File Size Uploaded
gunsnprices-0.1.1.tar.gz 133.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for gunsnprices 0.1.1
File Interpreter ABI Platform
gunsnprices-0.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 210.8 kB

Release files / gunsnprices-0.1.1.tar.gz

Download URL gunsnprices-0.1.1.tar.gz
Size 133.8 kB
Tags Source
SHA-256 checksum
How to use checksums
8c51c2367323bea7c27728deedf6d8cc432f0d777b7f9d5d9fc04013b69f5aa4
BLAKE2b-256 checksum
How to use checksums
eb9c712e5dfbceb7adbe454f241c0a38254cd97dd3644ae0abdbcee126e09056
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 12, 2026.

Transparency log

Release files / gunsnprices-0.1.1-py3-none-any.whl

Download URL gunsnprices-0.1.1-py3-none-any.whl
Size 77.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
fb2ced0a12b0113a768e24cc164f8c9116770878bc60bcd2fe6630ddd1473f17
BLAKE2b-256 checksum
How to use checksums
bb52fedc1013b3cb715472382f772ae821869db6ea845b0303d929254d2b4b3f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 12, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page