Zero-dependency Ukrainian declension of personal names, military ranks, and appointments (port of shevchenko-js). Import name: shevchenko.
Project description
shevchenko-py
Ukrainian grammatical-case declension of personal names, military ranks, and military appointments. A minimal, dependency-free Python port of shevchenko-js and shevchenko-ext-military.
- Zero runtime dependencies (pure Python ≥ 3.9, stdlib only)
- Tiny: ~30 KB wheel, ~116 KB installed (18 KB code + 98 KB data), 8 ms import
- All 7 Ukrainian grammatical cases
- Gender auto-detection from the patronymic or given name
- The upstream ML surname classifier reimplemented as ~30 lines of pure Python
Install
pip install shevchenko-py
The distribution is named shevchenko-py; the import name is shevchenko.
Don't install this alongside the unrelated shevchenko
PyPI package (a different port, anthroponyms only) — both provide the
shevchenko module.
Quick start
import shevchenko
shevchenko.in_genitive(
given_name="Тарас",
patronymic_name="Григорович",
family_name="Шевченко",
military_rank="старший солдат",
military_appointment="заступник командира взводу",
)
# {'given_name': 'Тараса', 'patronymic_name': 'Григоровича',
# 'family_name': 'Шевченка', 'military_rank': 'старшого солдата',
# 'military_appointment': 'заступника командира взводу'}
API reference
Everything is importable from the top-level shevchenko package. Anything
prefixed with _ (modules _inflector, _names, …) is internal and not a
stable interface.
Declension functions
shevchenko.in_nominative(input=None, **kwargs) -> dict # називний (хто? що?)
shevchenko.in_genitive(input=None, **kwargs) -> dict # родовий (кого? чого?)
shevchenko.in_dative(input=None, **kwargs) -> dict # давальний (кому? чому?)
shevchenko.in_accusative(input=None, **kwargs) -> dict # знахідний (кого? що?)
shevchenko.in_ablative(input=None, **kwargs) -> dict # орудний (ким? чим?)
shevchenko.in_locative(input=None, **kwargs) -> dict # місцевий (на кому? на чому?)
shevchenko.in_vocative(input=None, **kwargs) -> dict # кличний (звертання)
shevchenko.in_case(case, input=None, **kwargs) -> dict
All seven are thin wrappers around in_case with the case fixed. Upstream calls
the instrumental case (орудний) "ablative"; this port keeps that naming.
in_case(case, ...) takes the case as a string — one of shevchenko.CASES
("nominative", "genitive", "dative", "accusative", "ablative",
"locative", "vocative") — useful when the case is chosen at runtime:
for case in shevchenko.CASES:
print(shevchenko.in_case(case, gender="masculine", given_name="Тарас"))
Input
Fields may be passed as a dict, as keyword arguments, or both (keyword arguments override the dict):
shevchenko.in_vocative({"given_name": "Тарас"}, family_name="Шевченко")
# {'given_name': 'Тарасе', 'family_name': 'Шевченку'}
| Field | Meaning | Example |
|---|---|---|
given_name |
First name (ім'я) | "Тарас" |
patronymic_name |
Patronymic (по батькові) | "Григорович" |
family_name |
Surname (прізвище) | "Шевченко" |
military_rank |
Rank phrase (військове звання) | "старший солдат" |
military_appointment |
Appointment/position phrase (посада) | "заступник командира взводу" |
gender |
"masculine" / "feminine"; optional, see below |
"masculine" |
All fields are optional strings, but at least one non-gender field is
required. Unknown keys raise InputValidationError (so givenName= typos are
caught, not silently ignored). Values are NFC-normalized before matching.
The gender parameter
gender applies to the three name fields. If omitted, it is auto-detected from
the patronymic (preferred) or the given name — same logic as
detect_gender. Two consequences:
- Input with a patronymic or a recognizable given name never needs
gender. - A lone
family_nameusually can't be sexed — passgenderexplicitly orInputValidationErroris raised.
Military fields don't need gender at all: each word inside a rank or
appointment phrase carries its own grammatical gender (медична сестра
declines as feminine regardless of the person).
Return value
A dict containing only the fields you passed in (never gender), each
value inflected in the requested case. Field order is fixed: given_name,
patronymic_name, family_name, military_rank, military_appointment.
Behavior notes
-
Letter case is preserved:
ШЕВЧЕНКО→ШЕВЧЕНКА,Шевченко→Шевченка. -
Hyphenated names decline part by part (
Нечуй-Левицький→Нечуя-Левицького); monosyllabic non-final surname parts stay frozen (Драй-Хмара→Драй-Хмари). -
Unknown words in military phrases — proper names, abbreviations, qualifiers already in genitive — pass through unchanged:
shevchenko.in_ablative(military_appointment='оператор БПЛА "Фурія"') # {'military_appointment': 'оператором БПЛА "Фурія"'}
-
Unknown names that no declension rule matches are returned unchanged rather than guessed at.
Errors
InputValidationError (subclass of ValueError) is raised when:
caseis not one ofCASES(forin_case),- no field besides
genderis provided, - a field value is not a string,
- an unknown parameter is passed,
genderis neither"masculine","feminine", nor omitted,genderis omitted for name fields and cannot be auto-detected.
detect_gender
shevchenko.detect_gender(input=None, **kwargs) -> str | None
Detects the grammatical gender from patronymic_name (checked first) or
given_name endings. Accepts family_name too, but a surname alone is never
used for detection. Returns "masculine", "feminine", or None when
undetectable — unlike the declension functions it does not raise on ambiguity.
shevchenko.detect_gender(patronymic_name="Григорович") # 'masculine'
shevchenko.detect_gender(given_name="Оксана") # 'feminine'
shevchenko.detect_gender(family_name="Шевченко") # None
Input rules are the same as for declension: dict and/or kwargs, at least one
field, strings only, unknown keys raise InputValidationError.
Constants
| Constant | Value |
|---|---|
shevchenko.CASES |
("nominative", "genitive", "dative", "accusative", "ablative", "locative", "vocative") |
shevchenko.NOMINATIVE … shevchenko.VOCATIVE |
The individual case strings |
shevchenko.GENDERS |
("masculine", "feminine") |
shevchenko.MASCULINE, shevchenko.FEMININE |
The individual gender strings |
shevchenko.__version__ |
Package version string |
The constants are plain strings/tuples, so gender="feminine" and
gender=shevchenko.FEMININE are interchangeable.
InputValidationError
class InputValidationError(ValueError)
Raised by all public functions on invalid input; see Errors.
Performance
Measured on Python 3.14 with python benchmark.py (CPython, Windows, single
thread):
| Operation | Speed |
|---|---|
| Cold import (data load; regexes compile lazily) | ~8 ms, once |
| Full person (3 names + rank + appointment), 1 case | ~140 µs (≈7,000/s) |
| Single name field | ~20–24 µs (≈42–50,000/s) |
| Military rank / appointment phrase | ~40–50 µs (≈21–25,000/s) |
detect_gender |
~1 µs (≈700,000/s) |
| Ambiguous surname, first time (pure-Python RNN) | ~0.9 ms |
| Ambiguous surname, repeated (memoized) | ~10 µs |
| Real-corpus throughput (name triple, one case) | ≈13,000/s |
The only slow path is the first classification of an ambiguous surname (the neural network runs in pure Python); results are memoized, so each unique surname pays it once per process.
Fidelity
Tested against the complete upstream corpora (2334 fixtures: every anthroponym test case, every rank and appointment in all 7 cases, and the 1144-name gender detection dataset) and differentially verified against the live JS library on 3990 additional word/case combinations — zero mismatches. All upstream functional unit tests (letter case, syllables, alphabet encoding, classifier input encoding and decode threshold) are ported; upstream's input-validation suite is not, since this port's API surface deliberately differs.
How it works
Declension is rule-based: 99 regex rules from upstream (shevchenko/data/)
keyed by gender, word class, and usage, applied by priority. Surnames whose word
class is ambiguous (e.g. feminine -а/-я, masculine -ий/-ой/-их) are
classified noun-vs-adjective by the upstream neural network — an
Embedding→SimpleRNN(16)→Dense model small enough (4 KB) to run in pure Python
(shevchenko/_classifier.py). Two upstream regexes use variable-width
lookbehinds unsupported by Python's re and are rewritten to equivalent forms at
load time (shevchenko/_inflector.py).
The runtime data is regenerated from the upstream sources by
tools/build_data.py (dev-only): it strips documentation-only fields, drops
empty case entries, minifies, and verifies every pattern compiles under
Python's re. Data loading uses plain file reads instead of
importlib.resources, whose import chain alone costs ~25 ms.
Differences from shevchenko-js
| shevchenko-js | shevchenko-py | |
|---|---|---|
| Field names | givenName, familyName, … |
given_name, family_name, … |
| Military fields | separate extension, registerExtension(...) |
built in |
gender |
always required | auto-detected when omitted |
| API style | async/await |
plain synchronous calls |
| Validation errors | InputValidationError extends TypeError |
InputValidationError(ValueError) |
License
MIT. Declension rules, gender rules, model weights, and test corpora are from shevchenko-js © Oleksandr Tolochko et al., MIT-licensed.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
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
File details
Details for the file shevchenko_py-0.1.0.tar.gz.
File metadata
- Download URL: shevchenko_py-0.1.0.tar.gz
- Upload date:
- Size: 33.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b9ffd7c1e5a7b044462d1aa950222f91df457dab89a02ece2ecf91c434ff82e8
|
|
| MD5 |
cf0bad2b9977f5be51c6c223d57cd3bc
|
|
| BLAKE2b-256 |
24de267a48ffad4d0b14f9fcc6ab553fa9cf06090861e9ced6944e40c16e6ff1
|
File details
Details for the file shevchenko_py-0.1.0-py3-none-any.whl.
File metadata
- Download URL: shevchenko_py-0.1.0-py3-none-any.whl
- Upload date:
- Size: 30.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ce773b4bc4d11e07530c4be7dc3d4f96db8df917ef607c4ed5ae628a65372e22
|
|
| MD5 |
75106334d774c406d39cffec2678f611
|
|
| BLAKE2b-256 |
b95b7ac1231fd9bcbb618712cd60652f183f452d49d18fa22ed1aba495f17988
|