polish-inflection 0.1.0__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,41 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ .eggs/
6
+ build/
7
+ dist/
8
+ .venv/
9
+ venv/
10
+
11
+ # uv
12
+ uv.lock
13
+
14
+ # test / tooling
15
+ .pytest_cache/
16
+ .ruff_cache/
17
+ .coverage
18
+ htmlcov/
19
+
20
+ # scratch
21
+ *.tmp
22
+ scratchpad/
23
+
24
+ # macOS
25
+ .DS_Store
26
+ .AppleDouble
27
+ ._*
28
+
29
+ # Windows
30
+ Thumbs.db
31
+ ehthumbs.db
32
+ desktop.ini
33
+
34
+ # Linux / editors
35
+ *~
36
+ .directory
37
+
38
+ # IDE
39
+ .idea/
40
+ .vscode/
41
+ .history/
@@ -0,0 +1,24 @@
1
+ BSD 2-Clause License
2
+
3
+ Copyright (c) 2026, Michał Pasternak
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
19
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,68 @@
1
+ # NOTICE — licencje i atrybucja
2
+
3
+ Pakiet `polish-inflection` łączy dwie warstwy o **osobnych** właścicielach praw
4
+ autorskich i osobnych (choć identycznych co do treści) licencjach. Obie są
5
+ 2-clause BSD; obie wymagają zachowania noty copyright i tekstu licencji przy
6
+ redystrybucji.
7
+
8
+ ---
9
+
10
+ ## 1. Kod pakietu
11
+
12
+ Copyright © 2026 Michał Pasternak.
13
+ Licencja: **BSD-2-Clause**. Pełny tekst: plik [`LICENSE`](LICENSE) w tym repo.
14
+
15
+ Obejmuje kod źródłowy w `src/polish_inflection/` (runtime, build, CLI), testy
16
+ oraz dokumentację. **Nie** obejmuje danych fleksyjnych ani zbudowanych z nich
17
+ indeksów `.marisa` — te podlegają warstwie 2.
18
+
19
+ ---
20
+
21
+ ## 2. Dane SGJP (Słownik gramatyczny języka polskiego)
22
+
23
+ Indeksy `.marisa` w `src/polish_inflection/data/` są zbudowane z danych
24
+ fleksyjnych SGJP i stanowią ich utwór zależny. Podlegają licencji i atrybucji
25
+ SGJP.
26
+
27
+ **Copyright © 2007–2026 Marcin Woliński, Zbigniew Bronk, Włodzimierz
28
+ Gruszczyński, Witold Kieraś, Zygmunt Saloni, Danuta Skowrońska, Robert Wołosz.**
29
+
30
+ Licencja: **BSD-2-Clause**.
31
+ Strona licencyjna: <https://morfeusz.sgjp.pl/doc/license/en>.
32
+
33
+ Autorytatywny, verbatim tekst licencji jadący z konkretnym wydaniem SGJP
34
+ znajduje się w pliku **`data/sgjp/LICENSE.sgjp`** (wyodrębniony z nagłówka
35
+ `#<COPYRIGHT>` pobranego pliku `.tab.gz` przez komendę `refresh-sgjp`). W razie
36
+ jakiejkolwiek rozbieżności rozstrzygający jest tamten plik, nie ten dokument.
37
+
38
+ ### Warunki 2-clause BSD (cytat)
39
+
40
+ ```
41
+ Redistribution and use in source and binary forms, with or without
42
+ modification, are permitted provided that the following conditions are met:
43
+
44
+ 1. Redistributions of source code must retain the above copyright notice, this
45
+ list of conditions and the following disclaimer.
46
+
47
+ 2. Redistributions in binary form must reproduce the above copyright notice,
48
+ this list of conditions and the following disclaimer in the documentation
49
+ and/or other materials provided with the distribution.
50
+
51
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
52
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
53
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
54
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
55
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
56
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
57
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
58
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
59
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
60
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
61
+ ```
62
+
63
+ ### Konsekwencje redystrybucji
64
+
65
+ Redystrybuując `polish-inflection` (w tym wheel/sdist zawierające `.marisa`),
66
+ zachowujesz notę copyright SGJP, powyższe warunki i atrybucję — co realizuje ten
67
+ plik `NOTICE.md` (dołączany do dystrybucji) wraz z verbatim `data/sgjp/LICENSE.sgjp`
68
+ podróżującym z danymi w repozytorium.
@@ -0,0 +1,291 @@
1
+ Metadata-Version: 2.4
2
+ Name: polish-inflection
3
+ Version: 0.1.0
4
+ Summary: Lekka odmiana polskich rzeczowników przez przypadki (dane SGJP, indeks marisa-trie, mmap).
5
+ Project-URL: Homepage, https://github.com/mpasternak/polish-inflection
6
+ Project-URL: Repository, https://github.com/mpasternak/polish-inflection
7
+ Author-email: Michał Pasternak <m@iplweb.pl>
8
+ License: BSD-2-Clause
9
+ License-File: LICENSE
10
+ License-File: NOTICE.md
11
+ Keywords: declension,inflection,morphology,nlp,odmiana,polish,przypadki,sgjp
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: BSD License
15
+ Classifier: Natural Language :: Polish
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Text Processing :: Linguistic
23
+ Requires-Python: >=3.9
24
+ Requires-Dist: marisa-trie>=1.0
25
+ Provides-Extra: build
26
+ Requires-Dist: requests>=2.28; extra == 'build'
27
+ Description-Content-Type: text/markdown
28
+
29
+ # polish-inflection
30
+
31
+ [![CI](https://github.com/iplweb/polish-inflection/actions/workflows/ci.yml/badge.svg)](https://github.com/iplweb/polish-inflection/actions/workflows/ci.yml)
32
+ [![License: BSD-2-Clause](https://img.shields.io/badge/License-BSD--2--Clause-blue.svg)](LICENSE)
33
+
34
+ Lekka, dane-only odmiana polskich **rzeczowników** przez przypadki.
35
+ Dane pochodzą ze [Słownika gramatycznego języka polskiego (SGJP)](https://morfeusz.sgjp.pl/),
36
+ wyszukiwanie oparte jest o kompaktowy indeks `marisa-trie` (mmap, ~1 µs/lookup).
37
+ Instalacja bez kompilatora (gotowe binarne wheele), zero Django, jedna
38
+ zależność runtime.
39
+
40
+ ---
41
+
42
+ ## 🇵🇱 Wersja polska
43
+
44
+ ### Cel
45
+
46
+ Chcesz z wyrazu `wydział` dostać `wydziału` (dopełniacz), `wydziałowi`
47
+ (celownik), `wydziałów` (dopełniacz liczby mnogiej) — programowo, bez trzymania
48
+ form ręcznie w tabeli. `polish-inflection` odmienia rzeczowniki przez
49
+ **7 przypadków × 2 liczby** w obie strony:
50
+
51
+ - generacja: `lemat + przypadek + liczba → forma` (`odmien`);
52
+ - analiza (kierunek zwrotny): `forma → [analizy]` (`podaj`).
53
+
54
+ ### Dlaczego — luka w ekosystemie
55
+
56
+ Nie istniało lekkie, dane-only, przyjazne wdrożeniu rozwiązanie do
57
+ odmiany polskich rzeczowników przez przypadki:
58
+
59
+ - **gettext / formy mnogie** rozwiązują *inny* problem — wybór wariantu wg
60
+ **liczby** (`nplurals`), a nie odmianę przez **przypadki**. gettext nie zna
61
+ pojęcia przypadka i nie potrafi odmienić rzeczownika.
62
+ - **Morfeusz 2** to kanoniczny analizator i generator morfologii polskiej, ale
63
+ natywny silnik C++ + bindingi SWIG + kompilacja słownika rzędu dziesiątek MB.
64
+ Armata na muchę i ból wdrożeniowy w kontenerze. Tu nie ma silnika, SWIG-a ani
65
+ kompilacji słownika — tylko dane i `pip install`.
66
+ - **pymorphy2 / pymorphy3** obsługują tylko rosyjski i ukraiński — nie polski.
67
+ - **inflection / inflect / pyinflect** — tylko angielski.
68
+
69
+ Kluczowa idea: bierzemy **dane** SGJP (trójki `forma / lemat / tag`), a odrzucamy
70
+ **silnik**. Zbiór form jest zamknięty i w pełni wyliczony — nic nie generujemy
71
+ w locie, tylko indeksujemy i wyszukujemy.
72
+
73
+ ### Instalacja
74
+
75
+ ```bash
76
+ pip install polish-inflection
77
+ ```
78
+
79
+ Jedyna zależność runtime to `marisa-trie` — kompilowane rozszerzenie C++, które
80
+ dostarcza gotowe binarne wheele (Linux/macOS/Windows, cp39–cp313), więc
81
+ `pip install` nie potrzebuje kompilatora ani narzędzi build. Pakiet wiezie gotowe
82
+ indeksy `.marisa` w wheelu — bez SGJP. Czytnik używa **mmap**: import nie ładuje
83
+ słownika do RAM, a pojedynczy lookup stronicuje tylko O(długość słowa) węzłów
84
+ (~1 µs, mierzony narzut RSS ~1,5 MB zamiast ~23 MB pełnego wczytania).
85
+
86
+ ### Przykłady — `odmien`
87
+
88
+ ```python
89
+ from polish_inflection import odmien, DOPEŁNIACZ, MNOGA
90
+
91
+ odmien("wydział", DOPEŁNIACZ) # -> "wydziału"
92
+ odmien("wydział", DOPEŁNIACZ, MNOGA) # -> "wydziałów"
93
+ ```
94
+
95
+ Zachowanie przy słowie spoza słownika jest sterowane parametrem `default`:
96
+
97
+ ```python
98
+ from polish_inflection import (
99
+ odmien, odmien_lub_none, odmien_lub_wyraz, BrakOdmiany, DOPEŁNIACZ,
100
+ )
101
+
102
+ odmien("wydział", DOPEŁNIACZ) # "wydziału"
103
+ odmien("qwerty", DOPEŁNIACZ) # raise BrakOdmiany
104
+ odmien_lub_none("qwerty", DOPEŁNIACZ) # None
105
+ odmien_lub_wyraz("qwerty", DOPEŁNIACZ) # "qwerty" (passthrough wejścia)
106
+ odmien("qwerty", DOPEŁNIACZ, default="—") # "—" (dowolny fallback)
107
+ ```
108
+
109
+ Oboczności (kilka poprawnych form w jednym slocie) zwraca `odmien_warianty`:
110
+
111
+ ```python
112
+ from polish_inflection import odmien_warianty, MIEJSCOWNIK
113
+ odmien_warianty("pokój", MIEJSCOWNIK) # -> lista wszystkich poprawnych form
114
+ ```
115
+
116
+ ### Przykłady — `podaj` (kierunek zwrotny)
117
+
118
+ Polszczyzna ma synkretyzm (jedna forma = wiele przypadków) i homografię (jedna
119
+ forma = wiele lematów), więc `podaj` zwraca **listę** analiz:
120
+
121
+ ```python
122
+ from polish_inflection import podaj
123
+
124
+ podaj("jednostki")
125
+ # [Analiza(lemat='jednostka', przypadek='gen', liczba='sg', rodzaj='f'),
126
+ # Analiza(lemat='jednostka', przypadek='nom', liczba='pl', rodzaj='f'),
127
+ # Analiza(lemat='jednostka', przypadek='acc', liczba='pl', rodzaj='f'),
128
+ # Analiza(lemat='jednostka', przypadek='voc', liczba='pl', rodzaj='f')]
129
+
130
+ podaj("jednostki", liczba="pl") # zawężenie do liczby mnogiej
131
+ ```
132
+
133
+ `Analiza` to lekki `NamedTuple`: `(lemat, przypadek, liczba, rodzaj)`.
134
+
135
+ ### Stałe
136
+
137
+ Przypadki: `MIANOWNIK`, `DOPEŁNIACZ`, `CELOWNIK`, `BIERNIK`, `NARZĘDNIK`,
138
+ `MIEJSCOWNIK`, `WOŁACZ`. Liczby: `POJEDYNCZA`, `MNOGA` (domyślnie `POJEDYNCZA`).
139
+ Sentinel `TEN_SAM_WYRAZ` służy jako `default` zwracający wejściowy wyraz.
140
+
141
+ ### Źródło danych i atrybucja
142
+
143
+ Dane fleksyjne pochodzą ze **Słownika gramatycznego języka polskiego (SGJP)**,
144
+ w wersji przypiętej i zwendorowanej w repozytorium (patrz `data/sgjp/`).
145
+ Zbudowane indeksy pokrywają 223 748 lematów / ~3,86 mln rekordów form i ważą
146
+ łącznie ~49 MB (`odmien.marisa` ≈ 24 MB, `podaj.marisa` ≈ 25 MB).
147
+
148
+ > Copyright © 2007–2026 Marcin Woliński, Zbigniew Bronk, Włodzimierz
149
+ > Gruszczyński, Witold Kieraś, Zygmunt Saloni, Danuta Skowrońska, Robert Wołosz.
150
+
151
+ SGJP jest udostępniany na licencji 2-clause BSD. Strona licencyjna:
152
+ <https://morfeusz.sgjp.pl/doc/license/en>. Pełny tekst i szczegóły atrybucji —
153
+ patrz [`NOTICE.md`](NOTICE.md) oraz `data/sgjp/LICENSE.sgjp`.
154
+
155
+ ### Licencja
156
+
157
+ Dwie warstwy, jawnie rozdzielone:
158
+
159
+ - **Kod pakietu** — BSD-2-Clause, © Michał Pasternak (plik [`LICENSE`](LICENSE)).
160
+ - **Dane SGJP** — BSD-2-Clause, © autorzy wymienieni wyżej. Redystrybucja jest
161
+ dozwolona pod warunkiem zachowania noty copyright, tekstu licencji i atrybucji
162
+ (szczegóły w [`NOTICE.md`](NOTICE.md)).
163
+
164
+ ### Ograniczenia (v1)
165
+
166
+ - Tylko **rzeczowniki**. Bez czasowników, przymiotników, liczebników.
167
+ - Bez guessera słów spoza słownika i bez analizy biegnącego tekstu
168
+ (segmentacji/tokenizacji) — świadome YAGNI.
169
+ - Zakres: 7 przypadków × 2 liczby, oba kierunki (`odmien` / `podaj`).
170
+
171
+ ---
172
+
173
+ ## 🇬🇧 English version
174
+
175
+ ### Purpose
176
+
177
+ Turn `wydział` into `wydziału` (genitive), `wydziałowi` (dative), `wydziałów`
178
+ (genitive plural) — programmatically, without keeping inflected forms by hand.
179
+ `polish-inflection` declines Polish **nouns** across **7 cases × 2 numbers** in
180
+ both directions:
181
+
182
+ - generation: `lemma + case + number → form` (`odmien`);
183
+ - analysis (reverse): `form → [analyses]` (`podaj`).
184
+
185
+ ### Why — a gap in the ecosystem
186
+
187
+ There was no lightweight, data-only, deployment-friendly way to decline Polish
188
+ nouns by case:
189
+
190
+ - **gettext / plural forms** solve a *different* problem — picking a variant by
191
+ **number** (`nplurals`), not declension by **case**. gettext has no concept of
192
+ grammatical case and cannot decline a noun.
193
+ - **Morfeusz 2** is the canonical Polish morphological analyzer and generator,
194
+ but ships a native C++ engine + SWIG bindings + a compiled dictionary of tens
195
+ of MB. Overkill and a container-deployment headache. Here there is no engine,
196
+ no SWIG and no dictionary compilation — just data and `pip install`.
197
+ - **pymorphy2 / pymorphy3** cover only Russian and Ukrainian — not Polish.
198
+ - **inflection / inflect / pyinflect** — English only.
199
+
200
+ Core idea: take the SGJP **data** (`form / lemma / tag` triples), drop the
201
+ **engine**. The set of forms is closed and fully enumerated — we generate
202
+ nothing at runtime, we only index and look up.
203
+
204
+ ### Installation
205
+
206
+ ```bash
207
+ pip install polish-inflection
208
+ ```
209
+
210
+ The only runtime dependency is `marisa-trie` — a compiled C++ extension that
211
+ ships prebuilt binary wheels (Linux/macOS/Windows, cp39–cp313), so `pip install`
212
+ needs no compiler and no build tooling. The wheel ships prebuilt `.marisa`
213
+ indices — no SGJP required. The reader uses **mmap**: import does not load the
214
+ dictionary into RAM, and a single lookup pages in only O(word length) nodes
215
+ (~1 µs, measured RSS overhead ~1.5 MB instead of ~23 MB for a full load). The
216
+ indices cover 223,748 lemmas / ~3.86M form records and weigh ~49 MB total
217
+ (`odmien.marisa` ≈ 24 MB, `podaj.marisa` ≈ 25 MB).
218
+
219
+ ### Examples — `odmien`
220
+
221
+ ```python
222
+ from polish_inflection import odmien, DOPEŁNIACZ, MNOGA
223
+
224
+ odmien("wydział", DOPEŁNIACZ) # -> "wydziału"
225
+ odmien("wydział", DOPEŁNIACZ, MNOGA) # -> "wydziałów"
226
+ ```
227
+
228
+ Out-of-dictionary behaviour is controlled by `default`:
229
+
230
+ ```python
231
+ from polish_inflection import (
232
+ odmien, odmien_lub_none, odmien_lub_wyraz, BrakOdmiany, DOPEŁNIACZ,
233
+ )
234
+
235
+ odmien("wydział", DOPEŁNIACZ) # "wydziału"
236
+ odmien("qwerty", DOPEŁNIACZ) # raises BrakOdmiany
237
+ odmien_lub_none("qwerty", DOPEŁNIACZ) # None
238
+ odmien_lub_wyraz("qwerty", DOPEŁNIACZ) # "qwerty" (passthrough)
239
+ odmien("qwerty", DOPEŁNIACZ, default="—") # "—" (any fallback)
240
+ ```
241
+
242
+ `odmien_warianty` returns all valid forms in a slot (variants).
243
+
244
+ ### Examples — `podaj` (reverse direction)
245
+
246
+ Polish has syncretism (one form = many cases) and homography (one form = many
247
+ lemmas), so `podaj` returns a **list** of analyses:
248
+
249
+ ```python
250
+ from polish_inflection import podaj
251
+
252
+ podaj("jednostki")
253
+ # [Analiza(lemat='jednostka', przypadek='gen', liczba='sg', rodzaj='f'),
254
+ # Analiza(lemat='jednostka', przypadek='nom', liczba='pl', rodzaj='f'),
255
+ # Analiza(lemat='jednostka', przypadek='acc', liczba='pl', rodzaj='f'),
256
+ # Analiza(lemat='jednostka', przypadek='voc', liczba='pl', rodzaj='f')]
257
+
258
+ podaj("jednostki", liczba="pl") # narrow to plural
259
+ ```
260
+
261
+ `Analiza` is a lightweight `NamedTuple`: `(lemat, przypadek, liczba, rodzaj)`.
262
+ Constant names stay Polish (`MIANOWNIK`, `DOPEŁNIACZ`, …, `POJEDYNCZA`, `MNOGA`)
263
+ and map to SGJP tags (`nom`, `gen`, …, `sg`, `pl`).
264
+
265
+ ### Data source and attribution
266
+
267
+ The inflectional data comes from the **Grammatical Dictionary of Polish (SGJP)**,
268
+ pinned and vendored in this repository (see `data/sgjp/`).
269
+
270
+ > Copyright © 2007–2026 Marcin Woliński, Zbigniew Bronk, Włodzimierz
271
+ > Gruszczyński, Witold Kieraś, Zygmunt Saloni, Danuta Skowrońska, Robert Wołosz.
272
+
273
+ SGJP is distributed under the 2-clause BSD license. License page:
274
+ <https://morfeusz.sgjp.pl/doc/license/en>. Full text and attribution details are
275
+ in [`NOTICE.md`](NOTICE.md) and `data/sgjp/LICENSE.sgjp`.
276
+
277
+ ### License
278
+
279
+ Two clearly separated layers:
280
+
281
+ - **Package code** — BSD-2-Clause, © Michał Pasternak (see [`LICENSE`](LICENSE)).
282
+ - **SGJP data** — BSD-2-Clause, © the authors listed above. Redistribution is
283
+ permitted provided the copyright notice, license text and attribution are
284
+ retained (details in [`NOTICE.md`](NOTICE.md)).
285
+
286
+ ### Limitations (v1)
287
+
288
+ - **Nouns only.** No verbs, adjectives or numerals.
289
+ - No out-of-dictionary guesser and no running-text analysis
290
+ (segmentation/tokenization) — deliberate YAGNI.
291
+ - Scope: 7 cases × 2 numbers, both directions (`odmien` / `podaj`).
@@ -0,0 +1,263 @@
1
+ # polish-inflection
2
+
3
+ [![CI](https://github.com/iplweb/polish-inflection/actions/workflows/ci.yml/badge.svg)](https://github.com/iplweb/polish-inflection/actions/workflows/ci.yml)
4
+ [![License: BSD-2-Clause](https://img.shields.io/badge/License-BSD--2--Clause-blue.svg)](LICENSE)
5
+
6
+ Lekka, dane-only odmiana polskich **rzeczowników** przez przypadki.
7
+ Dane pochodzą ze [Słownika gramatycznego języka polskiego (SGJP)](https://morfeusz.sgjp.pl/),
8
+ wyszukiwanie oparte jest o kompaktowy indeks `marisa-trie` (mmap, ~1 µs/lookup).
9
+ Instalacja bez kompilatora (gotowe binarne wheele), zero Django, jedna
10
+ zależność runtime.
11
+
12
+ ---
13
+
14
+ ## 🇵🇱 Wersja polska
15
+
16
+ ### Cel
17
+
18
+ Chcesz z wyrazu `wydział` dostać `wydziału` (dopełniacz), `wydziałowi`
19
+ (celownik), `wydziałów` (dopełniacz liczby mnogiej) — programowo, bez trzymania
20
+ form ręcznie w tabeli. `polish-inflection` odmienia rzeczowniki przez
21
+ **7 przypadków × 2 liczby** w obie strony:
22
+
23
+ - generacja: `lemat + przypadek + liczba → forma` (`odmien`);
24
+ - analiza (kierunek zwrotny): `forma → [analizy]` (`podaj`).
25
+
26
+ ### Dlaczego — luka w ekosystemie
27
+
28
+ Nie istniało lekkie, dane-only, przyjazne wdrożeniu rozwiązanie do
29
+ odmiany polskich rzeczowników przez przypadki:
30
+
31
+ - **gettext / formy mnogie** rozwiązują *inny* problem — wybór wariantu wg
32
+ **liczby** (`nplurals`), a nie odmianę przez **przypadki**. gettext nie zna
33
+ pojęcia przypadka i nie potrafi odmienić rzeczownika.
34
+ - **Morfeusz 2** to kanoniczny analizator i generator morfologii polskiej, ale
35
+ natywny silnik C++ + bindingi SWIG + kompilacja słownika rzędu dziesiątek MB.
36
+ Armata na muchę i ból wdrożeniowy w kontenerze. Tu nie ma silnika, SWIG-a ani
37
+ kompilacji słownika — tylko dane i `pip install`.
38
+ - **pymorphy2 / pymorphy3** obsługują tylko rosyjski i ukraiński — nie polski.
39
+ - **inflection / inflect / pyinflect** — tylko angielski.
40
+
41
+ Kluczowa idea: bierzemy **dane** SGJP (trójki `forma / lemat / tag`), a odrzucamy
42
+ **silnik**. Zbiór form jest zamknięty i w pełni wyliczony — nic nie generujemy
43
+ w locie, tylko indeksujemy i wyszukujemy.
44
+
45
+ ### Instalacja
46
+
47
+ ```bash
48
+ pip install polish-inflection
49
+ ```
50
+
51
+ Jedyna zależność runtime to `marisa-trie` — kompilowane rozszerzenie C++, które
52
+ dostarcza gotowe binarne wheele (Linux/macOS/Windows, cp39–cp313), więc
53
+ `pip install` nie potrzebuje kompilatora ani narzędzi build. Pakiet wiezie gotowe
54
+ indeksy `.marisa` w wheelu — bez SGJP. Czytnik używa **mmap**: import nie ładuje
55
+ słownika do RAM, a pojedynczy lookup stronicuje tylko O(długość słowa) węzłów
56
+ (~1 µs, mierzony narzut RSS ~1,5 MB zamiast ~23 MB pełnego wczytania).
57
+
58
+ ### Przykłady — `odmien`
59
+
60
+ ```python
61
+ from polish_inflection import odmien, DOPEŁNIACZ, MNOGA
62
+
63
+ odmien("wydział", DOPEŁNIACZ) # -> "wydziału"
64
+ odmien("wydział", DOPEŁNIACZ, MNOGA) # -> "wydziałów"
65
+ ```
66
+
67
+ Zachowanie przy słowie spoza słownika jest sterowane parametrem `default`:
68
+
69
+ ```python
70
+ from polish_inflection import (
71
+ odmien, odmien_lub_none, odmien_lub_wyraz, BrakOdmiany, DOPEŁNIACZ,
72
+ )
73
+
74
+ odmien("wydział", DOPEŁNIACZ) # "wydziału"
75
+ odmien("qwerty", DOPEŁNIACZ) # raise BrakOdmiany
76
+ odmien_lub_none("qwerty", DOPEŁNIACZ) # None
77
+ odmien_lub_wyraz("qwerty", DOPEŁNIACZ) # "qwerty" (passthrough wejścia)
78
+ odmien("qwerty", DOPEŁNIACZ, default="—") # "—" (dowolny fallback)
79
+ ```
80
+
81
+ Oboczności (kilka poprawnych form w jednym slocie) zwraca `odmien_warianty`:
82
+
83
+ ```python
84
+ from polish_inflection import odmien_warianty, MIEJSCOWNIK
85
+ odmien_warianty("pokój", MIEJSCOWNIK) # -> lista wszystkich poprawnych form
86
+ ```
87
+
88
+ ### Przykłady — `podaj` (kierunek zwrotny)
89
+
90
+ Polszczyzna ma synkretyzm (jedna forma = wiele przypadków) i homografię (jedna
91
+ forma = wiele lematów), więc `podaj` zwraca **listę** analiz:
92
+
93
+ ```python
94
+ from polish_inflection import podaj
95
+
96
+ podaj("jednostki")
97
+ # [Analiza(lemat='jednostka', przypadek='gen', liczba='sg', rodzaj='f'),
98
+ # Analiza(lemat='jednostka', przypadek='nom', liczba='pl', rodzaj='f'),
99
+ # Analiza(lemat='jednostka', przypadek='acc', liczba='pl', rodzaj='f'),
100
+ # Analiza(lemat='jednostka', przypadek='voc', liczba='pl', rodzaj='f')]
101
+
102
+ podaj("jednostki", liczba="pl") # zawężenie do liczby mnogiej
103
+ ```
104
+
105
+ `Analiza` to lekki `NamedTuple`: `(lemat, przypadek, liczba, rodzaj)`.
106
+
107
+ ### Stałe
108
+
109
+ Przypadki: `MIANOWNIK`, `DOPEŁNIACZ`, `CELOWNIK`, `BIERNIK`, `NARZĘDNIK`,
110
+ `MIEJSCOWNIK`, `WOŁACZ`. Liczby: `POJEDYNCZA`, `MNOGA` (domyślnie `POJEDYNCZA`).
111
+ Sentinel `TEN_SAM_WYRAZ` służy jako `default` zwracający wejściowy wyraz.
112
+
113
+ ### Źródło danych i atrybucja
114
+
115
+ Dane fleksyjne pochodzą ze **Słownika gramatycznego języka polskiego (SGJP)**,
116
+ w wersji przypiętej i zwendorowanej w repozytorium (patrz `data/sgjp/`).
117
+ Zbudowane indeksy pokrywają 223 748 lematów / ~3,86 mln rekordów form i ważą
118
+ łącznie ~49 MB (`odmien.marisa` ≈ 24 MB, `podaj.marisa` ≈ 25 MB).
119
+
120
+ > Copyright © 2007–2026 Marcin Woliński, Zbigniew Bronk, Włodzimierz
121
+ > Gruszczyński, Witold Kieraś, Zygmunt Saloni, Danuta Skowrońska, Robert Wołosz.
122
+
123
+ SGJP jest udostępniany na licencji 2-clause BSD. Strona licencyjna:
124
+ <https://morfeusz.sgjp.pl/doc/license/en>. Pełny tekst i szczegóły atrybucji —
125
+ patrz [`NOTICE.md`](NOTICE.md) oraz `data/sgjp/LICENSE.sgjp`.
126
+
127
+ ### Licencja
128
+
129
+ Dwie warstwy, jawnie rozdzielone:
130
+
131
+ - **Kod pakietu** — BSD-2-Clause, © Michał Pasternak (plik [`LICENSE`](LICENSE)).
132
+ - **Dane SGJP** — BSD-2-Clause, © autorzy wymienieni wyżej. Redystrybucja jest
133
+ dozwolona pod warunkiem zachowania noty copyright, tekstu licencji i atrybucji
134
+ (szczegóły w [`NOTICE.md`](NOTICE.md)).
135
+
136
+ ### Ograniczenia (v1)
137
+
138
+ - Tylko **rzeczowniki**. Bez czasowników, przymiotników, liczebników.
139
+ - Bez guessera słów spoza słownika i bez analizy biegnącego tekstu
140
+ (segmentacji/tokenizacji) — świadome YAGNI.
141
+ - Zakres: 7 przypadków × 2 liczby, oba kierunki (`odmien` / `podaj`).
142
+
143
+ ---
144
+
145
+ ## 🇬🇧 English version
146
+
147
+ ### Purpose
148
+
149
+ Turn `wydział` into `wydziału` (genitive), `wydziałowi` (dative), `wydziałów`
150
+ (genitive plural) — programmatically, without keeping inflected forms by hand.
151
+ `polish-inflection` declines Polish **nouns** across **7 cases × 2 numbers** in
152
+ both directions:
153
+
154
+ - generation: `lemma + case + number → form` (`odmien`);
155
+ - analysis (reverse): `form → [analyses]` (`podaj`).
156
+
157
+ ### Why — a gap in the ecosystem
158
+
159
+ There was no lightweight, data-only, deployment-friendly way to decline Polish
160
+ nouns by case:
161
+
162
+ - **gettext / plural forms** solve a *different* problem — picking a variant by
163
+ **number** (`nplurals`), not declension by **case**. gettext has no concept of
164
+ grammatical case and cannot decline a noun.
165
+ - **Morfeusz 2** is the canonical Polish morphological analyzer and generator,
166
+ but ships a native C++ engine + SWIG bindings + a compiled dictionary of tens
167
+ of MB. Overkill and a container-deployment headache. Here there is no engine,
168
+ no SWIG and no dictionary compilation — just data and `pip install`.
169
+ - **pymorphy2 / pymorphy3** cover only Russian and Ukrainian — not Polish.
170
+ - **inflection / inflect / pyinflect** — English only.
171
+
172
+ Core idea: take the SGJP **data** (`form / lemma / tag` triples), drop the
173
+ **engine**. The set of forms is closed and fully enumerated — we generate
174
+ nothing at runtime, we only index and look up.
175
+
176
+ ### Installation
177
+
178
+ ```bash
179
+ pip install polish-inflection
180
+ ```
181
+
182
+ The only runtime dependency is `marisa-trie` — a compiled C++ extension that
183
+ ships prebuilt binary wheels (Linux/macOS/Windows, cp39–cp313), so `pip install`
184
+ needs no compiler and no build tooling. The wheel ships prebuilt `.marisa`
185
+ indices — no SGJP required. The reader uses **mmap**: import does not load the
186
+ dictionary into RAM, and a single lookup pages in only O(word length) nodes
187
+ (~1 µs, measured RSS overhead ~1.5 MB instead of ~23 MB for a full load). The
188
+ indices cover 223,748 lemmas / ~3.86M form records and weigh ~49 MB total
189
+ (`odmien.marisa` ≈ 24 MB, `podaj.marisa` ≈ 25 MB).
190
+
191
+ ### Examples — `odmien`
192
+
193
+ ```python
194
+ from polish_inflection import odmien, DOPEŁNIACZ, MNOGA
195
+
196
+ odmien("wydział", DOPEŁNIACZ) # -> "wydziału"
197
+ odmien("wydział", DOPEŁNIACZ, MNOGA) # -> "wydziałów"
198
+ ```
199
+
200
+ Out-of-dictionary behaviour is controlled by `default`:
201
+
202
+ ```python
203
+ from polish_inflection import (
204
+ odmien, odmien_lub_none, odmien_lub_wyraz, BrakOdmiany, DOPEŁNIACZ,
205
+ )
206
+
207
+ odmien("wydział", DOPEŁNIACZ) # "wydziału"
208
+ odmien("qwerty", DOPEŁNIACZ) # raises BrakOdmiany
209
+ odmien_lub_none("qwerty", DOPEŁNIACZ) # None
210
+ odmien_lub_wyraz("qwerty", DOPEŁNIACZ) # "qwerty" (passthrough)
211
+ odmien("qwerty", DOPEŁNIACZ, default="—") # "—" (any fallback)
212
+ ```
213
+
214
+ `odmien_warianty` returns all valid forms in a slot (variants).
215
+
216
+ ### Examples — `podaj` (reverse direction)
217
+
218
+ Polish has syncretism (one form = many cases) and homography (one form = many
219
+ lemmas), so `podaj` returns a **list** of analyses:
220
+
221
+ ```python
222
+ from polish_inflection import podaj
223
+
224
+ podaj("jednostki")
225
+ # [Analiza(lemat='jednostka', przypadek='gen', liczba='sg', rodzaj='f'),
226
+ # Analiza(lemat='jednostka', przypadek='nom', liczba='pl', rodzaj='f'),
227
+ # Analiza(lemat='jednostka', przypadek='acc', liczba='pl', rodzaj='f'),
228
+ # Analiza(lemat='jednostka', przypadek='voc', liczba='pl', rodzaj='f')]
229
+
230
+ podaj("jednostki", liczba="pl") # narrow to plural
231
+ ```
232
+
233
+ `Analiza` is a lightweight `NamedTuple`: `(lemat, przypadek, liczba, rodzaj)`.
234
+ Constant names stay Polish (`MIANOWNIK`, `DOPEŁNIACZ`, …, `POJEDYNCZA`, `MNOGA`)
235
+ and map to SGJP tags (`nom`, `gen`, …, `sg`, `pl`).
236
+
237
+ ### Data source and attribution
238
+
239
+ The inflectional data comes from the **Grammatical Dictionary of Polish (SGJP)**,
240
+ pinned and vendored in this repository (see `data/sgjp/`).
241
+
242
+ > Copyright © 2007–2026 Marcin Woliński, Zbigniew Bronk, Włodzimierz
243
+ > Gruszczyński, Witold Kieraś, Zygmunt Saloni, Danuta Skowrońska, Robert Wołosz.
244
+
245
+ SGJP is distributed under the 2-clause BSD license. License page:
246
+ <https://morfeusz.sgjp.pl/doc/license/en>. Full text and attribution details are
247
+ in [`NOTICE.md`](NOTICE.md) and `data/sgjp/LICENSE.sgjp`.
248
+
249
+ ### License
250
+
251
+ Two clearly separated layers:
252
+
253
+ - **Package code** — BSD-2-Clause, © Michał Pasternak (see [`LICENSE`](LICENSE)).
254
+ - **SGJP data** — BSD-2-Clause, © the authors listed above. Redistribution is
255
+ permitted provided the copyright notice, license text and attribution are
256
+ retained (details in [`NOTICE.md`](NOTICE.md)).
257
+
258
+ ### Limitations (v1)
259
+
260
+ - **Nouns only.** No verbs, adjectives or numerals.
261
+ - No out-of-dictionary guesser and no running-text analysis
262
+ (segmentation/tokenization) — deliberate YAGNI.
263
+ - Scope: 7 cases × 2 numbers, both directions (`odmien` / `podaj`).