Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions .github/workflows/python-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest]
python-version: ['3.9', '3.10', '3.11', '3.12', '3.13', '3.14', 'pypy-3.11']
python-version: ['3.11', '3.12', '3.13', '3.14', 'pypy-3.11']

steps:
- uses: actions/checkout@v4
Expand All @@ -28,19 +28,19 @@ jobs:
python -m pip install uv
uv pip install --system --upgrade wheel setuptools pytest tokenizer reynir
# No need to test the sentence classifier in every build (also doesn't work with PyPy)
if [ "${{ matrix.python-version }}" == "3.9" ]; then
if [ "${{ matrix.python-version }}" == "3.11" ]; then
uv pip install --system -e ".[sentence_classifier]"
else
uv pip install --system -e ".[dev]"
fi
- name: Lint with ruff
run: |
if [ "${{ matrix.python-version }}" == "3.9" ]; then uv pip install --system ruff; fi
if [ "${{ matrix.python-version }}" == "3.9" ]; then ruff check src/reynir_correct; fi
if [ "${{ matrix.python-version }}" == "3.11" ]; then uv pip install --system ruff; fi
if [ "${{ matrix.python-version }}" == "3.11" ]; then ruff check src/reynir_correct; fi
- name: Typecheck with mypy
run: |
if [ "${{ matrix.python-version }}" == "3.9" ]; then uv pip install --system mypy; fi
if [ "${{ matrix.python-version }}" == "3.9" ]; then mypy --ignore-missing-imports --python-version=3.9 src/reynir_correct; fi
if [ "${{ matrix.python-version }}" == "3.11" ]; then uv pip install --system mypy; fi
if [ "${{ matrix.python-version }}" == "3.11" ]; then mypy --ignore-missing-imports --python-version=3.11 src/reynir_correct; fi
- name: Test with pytest
run: |
python -m pytest
9 changes: 5 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ classifiers = [
"Natural Language :: Icelandic",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
Expand All @@ -28,7 +26,7 @@ classifiers = [
"Topic :: Utilities",
"Topic :: Text Processing :: Linguistic",
]
requires-python = ">=3.9"
requires-python = ">=3.11"
dependencies = ["reynir>=3.5.7", "icegrams>=1.1.2", "typing_extensions"]

[project.urls]
Expand Down Expand Up @@ -62,7 +60,10 @@ filterwarnings = [
line-length = 120

[tool.ruff.lint]
#select = ["ALL"] # We use default rules for now
# Pin the rule selection (this was ruff's default selection before
# version 0.15; later versions enable many more rules by default)
select = ["E4", "E7", "E9", "F"]
#select = ["ALL"]
# extend-select = ["E501"] # Complain about line length
# Ignore specific rules
# (we should aim to have these as few as possible)
Expand Down
5 changes: 5 additions & 0 deletions src/reynir_correct/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,11 @@ def __init__(
pipeline: CorrectionPipeline,
**options: Any,
) -> None:
# Greynir (reynir >= 3.7) rejects unknown keyword arguments;
# only pass through the options that it recognizes
known = getattr(self, "_KNOWN_OPTIONS", None)
if known is not None:
options = {k: v for k, v in options.items() if k in known}
super().__init__(**options)
self.settings = settings
self.pipeline = pipeline
Expand Down
2 changes: 1 addition & 1 deletion src/reynir_correct/classifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ def __init__(self) -> None:
)
warnings.warn(warningtext)
raise ImportError(warningtext)
self.pipe: Any = pipeline(
self.pipe: Any = pipeline( # type: ignore[call-overload]
"text2text-generation",
model=self._model_name,
tokenizer="google/byt5-base",
Expand Down
40 changes: 40 additions & 0 deletions src/reynir_correct/errfinder.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,14 @@ def fget(self, tree: SimpleTree) -> str:
# Case name prefixes
CASE_NAMES = {"nf": "nefni", "þf": "þol", "þgf": "þágu", "ef": "eignar"}

# Adjective inflection variants other than case, kept unchanged when
# suggesting a corrected case form. Note that strong/weak declension
# variants ('sb'/'vb'/'esb'/'evb') are deliberately not included, as
# BinPackage's lookup_variants() does not map them onto the FSB/FVB/
# ESB/EVB inflection marks; BÍN returns strong (FSB) forms first,
# which is the standard declension in this construction anyway.
ADJECTIVE_NON_CASE_VARIANTS = frozenset(("et", "ft", "kk", "kvk", "hk", "mst"))

# Replacements for numeric ordinals, in the various genders and cases
ORDINALS = {
1: {
Expand Down Expand Up @@ -552,6 +560,38 @@ def VillaEinkunn(self, txt: str, variants: str, node: Node) -> AnnotationDict:
suggest=correct_pronoun,
)

def VillaLoEftirNlMeðAndlagi(self, txt: str, variants: str, node: Node) -> Optional[AnnotationDict]:
# Lýsingarorð með fallstýrðu andlagi, á eftir nafnlið sem það
# sambeygist ekki í falli, t.d.
# 'móðurfélag fleiri félaga tengdum rekstri' -> 'tengdra'
correct_case = variants.split("_")[0]
# The offending adjective is the first terminal within the error phrase
tnode = self._terminal_nodes[node.start]
wrong_adj = tnode.text
# Preserve the adjective's other inflection features,
# replacing only the case
keep = [v for v in tnode.all_variants if v in ADJECTIVE_NON_CASE_VARIANTS]
suggestion = PatternMatcher.get_wordform(
wrong_adj.lower(), tnode.lemma, tnode.cat, keep + [correct_case]
)
if not suggestion or suggestion == wrong_adj.lower():
# Can't find a correct form, or it's identical to the original:
# don't annotate
return None
suggestion = emulate_case(suggestion, template=wrong_adj)
start, end = tnode.span
return AnnotationDict(
text="'{0}' á sennilega að vera '{1}'".format(wrong_adj, suggestion),
detail=(
"Lýsingarorðið '{0}' á að vera í {1}falli, í samræmi við "
"nafnliðinn á undan".format(wrong_adj, CASE_NAMES[correct_case])
),
start=start,
end=end,
original=wrong_adj,
suggest=suggestion,
)

def AðvörunSemOg(self, txt: str, variants: str, node: Node) -> AnnotationDict:
# 'sem' er sennilega ofaukið
ch1, ch2 = self._terminal_nodes[node.start : node.end]
Expand Down
24 changes: 24 additions & 0 deletions test/test_annotator.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,30 @@ def test_corrected_meanings(api) -> None:
)


def test_adjective_agreement(api) -> None:
"""Check annotation of a postposed adjective with a case-governed
complement that does not agree with its head noun phrase in case:
'forstöðumenn fyrirtækja tengdum sjávarútvegi' -> 'tengdra'"""
nts = api.gc.parser.grammar.nonterminals
if not any(name.startswith("VillaLoEftirNlMeðAndlagi") for name in nts):
# The installed reynir version does not include the
# VillaLoEftirNlMeðAndlagi error grammar rule
pytest.skip("reynir version lacks the VillaLoEftirNlMeðAndlagi grammar rule")
s = "Ég ræddi við forstöðumenn fyrirtækja tengdum sjávarútvegi."
check_sentence(api, s, [(5, 5, "P_NT_LoEftirNlMeðAndlagi")])
# The corresponding correct sentence should not be annotated
s = "Ég ræddi við forstöðumenn fyrirtækja tengdra sjávarútvegi."
check_sentence(api, s, [])
# Check the suggested correction
sent = reynir_correct.check_single(
"Ég ræddi við forstöðumenn fyrirtækja tengdum sjávarútvegi."
)
anns = [a for a in sent.annotations if a.code == "P_NT_LoEftirNlMeðAndlagi"]
assert len(anns) == 1
assert anns[0].suggest == "tengdra"
assert anns[0].original == "tengdum"


def test_lhþt_variant(api) -> None:
"""Check for a regression in the handling of LHÞT variants in BinPackage"""
s = (
Expand Down
Loading