Skip to content

jqueguiner/faker2

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4,366 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

faker2 — a Rust port of Python Faker (+ improvements)

CI Coverage codecov PyPI Python versions Rust release License: MIT

faker2 is a port of the Python Faker project to Rust, with a thin Python binderimport faker2 runs the compiled Rust engine (via PyO3); no fake-data logic lives in Python. It keeps Faker's 151 locales, generates the same kinds of data ~3–18× faster, and adds name-intelligence features the original doesn't have.

import faker2
f = faker2.Faker(42)                  # seedable, deterministic
f.gen("fr_FR", "name")                # 'Édouard Guichard'
f.gen("ja_JP", "address")             # Japanese address
f.gen("en_US", "credit_card_number")  # Luhn-valid
f.homophones("Dominique", "FR")       # (name, probability, phonetic_similarity)

Upstream Faker docs: README.upstream.rst. Python ↔ Rust parity matrix: PARITY.md.

Install

Python — from PyPI (Rust-backed wheels, abi3, for Linux x86_64/aarch64, macOS Intel/Apple-Silicon, Windows):

pip install faker2

Rust — the crate (git for now):

cargo add faker2 --git https://github.com/jqueguiner/faker2 --features real-names,locales

Prebuilt CLI binaries and wheels are attached to every GitHub Release.

Python API (the binder)

import faker2 loads the compiled Rust core (faker2._native, PyO3). No fake-data logic runs in Python.

import faker2

f = faker2.Faker(42)                       # seedable; Faker() for random
f.gen("fr_FR", "name")                     # any of 151 locales, ~213 formatters
f.email(); f.address(); f.company()        # en_US convenience methods

# name intelligence (real 1.43M-name, 139-country dataset)
f.first_name_like_real("Jacques", "FR")    # gender-preserving, freq-weighted
faker2.infer_gender_real("Mohammed", "EG") # "m"
faker2.detect_country("Yuki")              # [("JP", 0.58), ...]
faker2.homophones("Dominique", "FR", "balanced", 5, True, None)
#   -> [("Dominique", 0.94, 1.0), ("Dominic", 0.02, 0.80), ...]  (name, prob, similarity)
faker2.locales()                           # 151

Module functions: infer_gender_real, detect_country, homophones, available_countries, locales, locale_formatters. Class: Faker.

Rust API

use faker2::Faker;

let f = Faker::seeded(42);
f.gen("ja_JP", "name");                          // -> Option<String>, 151 locales
f.first_name_like_real("Jacques", Some("FR"));   // needs the `real-names` feature
Faker::homophones("Dominique", "FR", "balanced", 5, true, None);
//   -> Vec<(String /*name*/, f64 /*prob*/, f64 /*similarity*/)>
Faker::detect_country("Yuki", 3);

Features: real-names (parquet name dataset + homophones), locales (151-locale gen). See rust/README.md.

Repository layout

faker2/                 Python package (upstream faker fork)
  naming/               ← added: name intelligence
    gender.py             gender infer + preserving replace (bundled locales)
    realnames.py          same, backed by data/ ground truth (needs pyarrow)
    grammar.py            pluralize / singularize / agreement
data/                   ← added: name ground truth
  first_names.parquet     1.43M names, 139 countries, gender, relative frequency
  last_names.parquet      small sample (surnames NOT comprehensive)
  README.md               schema + provenance
rust/                   ← added: Rust port
  src/                    engine, providers, gender, grammar, realnames
  README.md               crate docs
tests/                  Python tests (incl. test_gender_grammar.py)

Python — name intelligence

from faker2.naming import realnames, grammar

realnames.infer_gender("Jacques", "FR")       # "m"
realnames.first_name_like("Jacques", "FR")    # frequency-weighted male FR name
realnames.first_name("JP", "f")               # weighted female Japanese name
realnames.detect_country("Yuki")              # [("JP", 0.58), ("CN", 0.05), ...]
realnames.detect_country("Bjorn")             # [("SE", 0.29), ("NO", 0.22), ...]
realnames.homophones("Dominique", "FR")       # [("Dominique", 0.94, 1.0), ("Dominic", 0.02, 0.80), ...]  (name, prob, g2p similarity)

grammar.pluralize("baby")                     # "babies"
grammar.agree(3, "dog")                       # "3 dogs"

realnames needs pyarrow (in dev-requirements.txt). The bundled-locale variant faker2.naming.gender has no extra dependency.

detect_country ranks where a name is most characteristic (its within-country frequency share), not where the most people with that name live — raw population counts are intentionally not in the dataset.

homophones returns same-sounding names in a country as (name, probability, similarity) triples — probability = frequency share within the group (sums to 1); similarity = g2p2 per-language phonetic similarity (0..1) of that name to the query. Pick the matching method:

  • "metaphone" (default) — double-metaphone group; fast but coarse (may pull in near-homophones, e.g. Sophie↔Xavier).
  • "ipa"per-language phonetics: every name in the dataset is phonemized in its own language with g2p2 (the g2p_ipa column) and scored with g2p2's articulatory feature-weighted alignment — so Mohammed→Mohammad, Giovanni→Gianni use real Arabic/Italian phonetics, not an anglocentric transcription.
  • "levenshtein" — spelling within max_distance edits; orthographic variants.
  • "balanced" — g2p2 phonetics + spelling consensus with per-country weights swept offline (data/balanced_params.json, 119 countries). Drops metaphone-only collisions (Sophie↛Xavier).
realnames.homophones("Dominique", "FR", method="ipa")          # (name, prob, similarity)
realnames.homophones("Marc", "FR", method="levenshtein", max_distance=1)
realnames.homophones("Sophie", "FR", method="balanced")        # per-country tuned; no Xavier

The balanced weights are tuned by scripts/sweep_balanced.py, which grid-searches (w_ipa, min, cap) per country against a spelling+metaphone consensus objective, using g2p2 similarity on the per-language g2p_ipa. Regenerate with PYTHONPATH=. python3 scripts/sweep_balanced.py (needs g2p2, pyarrow).

Rust port

cd rust
cargo test                          # zero-dependency core
cargo run -- --seed 42 name
cargo run -- like Jacques fr        # gender-preserving replacement
cargo test --features real-names    # opt-in parquet ground truth

See rust/README.md.

faker2 (this port) vs Faker (Python)

Original Faker = the pure-Python package (from faker2 import Faker, the fork of joke2k/Faker). This port = the Rust engine (import faker2, or the crate directly). Same machine, en_US.

Full per-formatter table (63 formatters): BENCHMARKS.md — 62/63 are faster in Rust, geo-mean ~20×.

Speed — throughput (higher is better)

Formatter Python Rust Speedup
name 0.017 M ops/s 0.315 M ops/s ~18×
email 0.017 M ops/s 0.296 M ops/s ~17×
address 0.012 M ops/s 0.162 M ops/s ~13×
credit_card_number (Luhn) 0.084 M ops/s 0.292 M ops/s ~3.5×
iban (mod-97) 0.068 M ops/s 0.344 M ops/s ~5×

Hardware — 500k names, /usr/bin/time -v

Metric Python Rust Note
CPU / wall time 29 s 2 s ~15× faster
Peak RAM (RSS) 26 MB 120 MB Rust eagerly loads all 151 locales (flat); Python lazy-loads one

RAM is the one honest trade-off: the Rust engine holds the entire 151-locale dataset resident (constant ~120 MB) for instant locale switching, whereas Python loads a locale lazily. For multi-locale bulk work Rust's footprint stays flat while Python's grows per locale.

Accuracy — generated data validity (2000 samples each)

Check Python Rust
Credit-card Luhn 100% 100%
IBAN ISO-7064 mod-97 100% 100%
EAN-13 GS1 checksum 100% 100%

Identical: both produce valid, checksum-correct data. The Rust checksums are independently verified in rust/tests/algo.rs.

Coverage

Python (original) Rust (this port)
Locales 151 151
Formatters ~230 213 (93%)

Every locale is covered, and 100% of the single-string formatters are ported (name, address, credit cards, IBAN, barcodes, colors, dates, network, python scalars, geo, doi, passport, user-agent, lorem, gendered names, json/csv/xml, …). The ~17 remaining return non-string data — raw bytes (binary, image, json_bytes, tar, zip), Python collections (pylist, pydict, pyset, profile, …) or need an argument (enum) — not applicable to a single-string generator.

Improvements over the original Faker

Name intelligence backed by a real 1.43M-name / 139-country dataset — not in upstream Faker:

  • infer_gender_real(name, country) — gender from real data.
  • first_name_like(name, country) — gender-preserving, frequency-weighted replacement.
  • detect_country(name) — most likely origin country of a name.
  • homophones(name, country, method=…) — same-sounding names + probabilities (metaphone / IPA / levenshtein / balanced, per-country tuned).
  • grammar agreement (pluralize / singularize / count agreement).

Reproduce

# Python (original)
PYTHONPATH=. python3 - <<'PY'
from faker2 import Faker; import time
f=Faker("en_US"); n=200000; t=time.perf_counter()
for _ in range(n): f.name()
print(n/(time.perf_counter()-t)/1e6, "M ops/s")
PY
# Rust (this port)
cd rust && cargo run --release --features locales --example benchfull   # if kept

Data provenance

The name dataset was extracted from a PostgreSQL dump and normalized so frequency is a relative share — no raw population counts are exposed. See data/README.md.

About

Standalone fork of joke2k/Faker — 100 languages + name intelligence (gender/country/homophones over 1.43M real names) and a Rust port. Docs: https://jqueguiner.github.io/faker2/

Resources

Contributing

Stars

Watchers

Forks

Releases

Packages

Used by

Contributors

Languages