Parse, validate and extract data from Egyptian National ID numbers
Project description
egypt-nid
egypt-nid is the definitive Python library for parsing, validating, and extracting structured data from Egyptian National ID numbers (الرقم القومي).
Features
- ✅ Full 14-digit structural validation (century, birth date, governorate, Luhn checksum)
- 🔒 Strict mode (raises typed exceptions) and lenient mode (collects warnings)
- 📊 Bulk parsing, filtering, and aggregate statistics
- 🌍 Governorate names in English and Arabic with locale support
- 🎭 Masking for safe display
- 🖥️ CLI tool (
egypt-nid) - 🔌 Framework-ready (Django, FastAPI, Pandas — no hard dependencies)
- 📦
py.typed– PEP 561 compliant, fully typed - ⚡ LRU-cached repeated parses
Installation
pip install egypt-nid
Quick Start
from egypt_nid import parse
nid = parse("30105150123456")
print(nid.birth_date) # datetime.date(2001, 5, 15)
print(nid.age) # 23 (depends on current date)
print(nid.gender) # 'male'
print(nid.governorate_name) # 'Cairo'
print(nid.governorate_name_ar) # 'القاهرة'
print(nid.is_adult()) # True
print(nid.born_outside_egypt) # False
print(nid.masked()) # '3** ** ** **** 3456'
Error Handling
Strict mode (default)
from egypt_nid import parse
from egypt_nid.exceptions import (
LengthError, NonNumericError, CenturyError,
BirthDateError, GovernorateError, ChecksumError,
)
try:
nid = parse("bad_input")
except LengthError as e:
print(f"Wrong length: {e}")
except ChecksumError as e:
print(f"Typo detected: {e}")
except EgyptNIDError as e:
print(f"Validation failed: {e}")
Lenient mode
from egypt_nid import parse_lenient
nid, result = parse_lenient("30105150123457") # bad checksum
if not result.valid:
for field, warning in result.warnings.items():
print(f" [{field}] {warning}")
# nid is still returned if only non-critical checks fail
Serialisation
nid = parse("30105150123456")
# Dictionary
d = nid.to_dict()
d = nid.to_dict(locale="ar", include_birth_date=False)
# JSON
print(nid.to_json())
print(nid.to_json(indent=None)) # compact
Bulk Operations
from egypt_nid import parse_bulk, compute_stats
from egypt_nid import filter_by_gender, filter_by_age_range, filter_adults
ids = ["30105150123456", "29008200201241", ...]
parsed = parse_bulk(ids, skip_errors=True)
males = filter_by_gender(parsed, "male")
adults = filter_adults(parsed)
seniors = filter_by_age_range(parsed, 60, 100)
stats = compute_stats(parsed)
print(stats.average_age) # 34.7
print(stats.gender_distribution) # {'male': 120, 'female': 80}
print(stats.governorate_distribution) # {'01': 50, '21': 30, ...}
CLI Usage
# Pretty output
egypt-nid 30105150123456
# JSON output
egypt-nid --json 30105150123456
# Lenient mode
egypt-nid --lenient 30105150123457
# Bulk processing from file (one NID per line)
egypt-nid --bulk ids.txt
egypt-nid --bulk ids.txt --json
# Version
egypt-nid --version
Framework Integration
Django
from django.core.exceptions import ValidationError
from egypt_nid import parse
from egypt_nid.exceptions import EgyptNIDError
def validate_egyptian_nid(value):
try:
parse(value)
except EgyptNIDError as exc:
raise ValidationError(str(exc)) from exc
FastAPI
from fastapi import HTTPException
from egypt_nid import parse, NationalID
from egypt_nid.exceptions import EgyptNIDError
def get_nid(nid_str: str) -> NationalID:
try:
return parse(nid_str)
except EgyptNIDError as exc:
raise HTTPException(status_code=422, detail=str(exc))
Pandas
import pandas as pd
from egypt_nid import parse
from egypt_nid.exceptions import EgyptNIDError
def safe_parse(val):
try:
return parse(val).gender
except EgyptNIDError:
return None
df["gender"] = df["nid"].apply(safe_parse)
Custom Governorates
from egypt_nid import parse
custom = {
"01": ("New Cairo", "القاهرة الجديدة"),
"88": ("Foreign", "خارج الجمهورية"),
}
nid = parse("30105150123456", custom_governorates=custom)
Governorate Versioning
# Default is "v1"; future versions will be added without breaking changes
nid = parse("30105150123456", version="v1")
Performance Notes
- Repeated parses of the same NID string are served from an LRU cache (4096 entries).
parse_bulkprocesses a list in a single pass.- All derived attributes (age, gender) are computed lazily.
- For datasets > 100 k rows, consider running in a thread pool executor.
Contributing
Please read CONTRIBUTING.md and open a pull request. All contributions require:
- Tests (pytest + Hypothesis)
- Type annotations
- Google-style docstrings
- Passing
ruff,black, andmypy --strict
License
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
egypt_nid-0.1.1.tar.gz
(26.5 kB
view details)
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
egypt_nid-0.1.1-py3-none-any.whl
(22.9 kB
view details)
File details
Details for the file egypt_nid-0.1.1.tar.gz.
File metadata
- Download URL: egypt_nid-0.1.1.tar.gz
- Upload date:
- Size: 26.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.11
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e19f7d0897c6a0bee93e3552bd0fdb0024d4557867d02fc6d03af21fad337632
|
|
| MD5 |
33aa34eac25480bba7d8107509e9feeb
|
|
| BLAKE2b-256 |
d20787306ad3126cb12bdb6384541813a789e185ddbd22e3d80ee6090dc86d99
|
File details
Details for the file egypt_nid-0.1.1-py3-none-any.whl.
File metadata
- Download URL: egypt_nid-0.1.1-py3-none-any.whl
- Upload date:
- Size: 22.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.11
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d63515e6198e4d79f475e6203e04544bf37cad1997e999ec31e98cfc349231f5
|
|
| MD5 |
814d0568996e9097fe4708f7fe61582e
|
|
| BLAKE2b-256 |
6da53dfe0ab6eff98b4c3c1be003f998bfac26864d9ace92e8fb539dc922d19b
|