tugatagger 0.0.1__py3-none-any.whl
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.
tugatagger/__init__.py
ADDED
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
from typing import List, Tuple
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class TugaTagger:
|
|
5
|
+
"""
|
|
6
|
+
A unified interface for Portuguese Part-of-Speech (POS) tagging.
|
|
7
|
+
|
|
8
|
+
Supports multiple backends including spaCy and Brill-style taggers.
|
|
9
|
+
The 'auto' mode provides a fallback mechanism to ensure tagging works
|
|
10
|
+
even if specific dependencies are missing.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
def __init__(self, engine: str = "auto", spacy_model: str = "pt_core_news_lg"):
|
|
14
|
+
"""
|
|
15
|
+
Create a TugaTagger configured for the chosen tagging engine and optionally preload backend models.
|
|
16
|
+
|
|
17
|
+
Parameters:
|
|
18
|
+
engine (str): Tagging engine to use: "spacy", "brill", "dummy", or "auto".
|
|
19
|
+
- "spacy": preload the spaCy model in strict mode.
|
|
20
|
+
- "brill": preload the Brill tagger in strict mode.
|
|
21
|
+
- "auto": attempt to preload both spaCy and Brill (not strict).
|
|
22
|
+
spacy_model (str): Name of the spaCy Portuguese model to load when using spaCy (default "pt_core_news_lg").
|
|
23
|
+
|
|
24
|
+
Notes:
|
|
25
|
+
Load failures for a backend will be propagated when that backend is loaded in strict mode.
|
|
26
|
+
"""
|
|
27
|
+
self.engine = engine
|
|
28
|
+
self._spacy = self._brill = self._lexicon = None
|
|
29
|
+
|
|
30
|
+
if engine in ["spacy", "auto"]:
|
|
31
|
+
self.load_spacy(spacy_model, strict=(engine == "spacy"))
|
|
32
|
+
if engine in ["brill", "auto"]:
|
|
33
|
+
self.load_brill(strict=(engine == "brill"))
|
|
34
|
+
if engine in ["lexicon", "auto"]:
|
|
35
|
+
self.load_lexicon(strict=(engine == "lexicon"))
|
|
36
|
+
|
|
37
|
+
def load_lexicon(self, strict: bool = True):
|
|
38
|
+
try:
|
|
39
|
+
from tugalex import TugaLexicon
|
|
40
|
+
self._lexicon = TugaLexicon()
|
|
41
|
+
except Exception as e:
|
|
42
|
+
if strict:
|
|
43
|
+
raise e
|
|
44
|
+
|
|
45
|
+
def load_spacy(self, spacy_model: str = "pt_core_news_lg", strict: bool = True):
|
|
46
|
+
"""
|
|
47
|
+
Load and cache a spaCy Portuguese NLP model on the instance.
|
|
48
|
+
|
|
49
|
+
Parameters:
|
|
50
|
+
spacy_model (str): Name of the spaCy model to load (e.g., "pt_core_news_lg").
|
|
51
|
+
strict (bool): If True, re-raise any exception encountered while loading; if False, suppress the exception and leave `self._spacy` as None.
|
|
52
|
+
|
|
53
|
+
Side effects:
|
|
54
|
+
Assigns the loaded spaCy model to `self._spacy`.
|
|
55
|
+
"""
|
|
56
|
+
try:
|
|
57
|
+
import spacy
|
|
58
|
+
self._spacy = spacy.load(spacy_model, disable=["ner", "parser"])
|
|
59
|
+
except Exception as e:
|
|
60
|
+
if strict:
|
|
61
|
+
raise e
|
|
62
|
+
|
|
63
|
+
def load_brill(self, strict: bool = True):
|
|
64
|
+
"""
|
|
65
|
+
Load and cache a Brill-style Portuguese POS tagger on the instance.
|
|
66
|
+
|
|
67
|
+
Parameters:
|
|
68
|
+
strict (bool): If True, re-raise any exception encountered while importing or loading the tagger;
|
|
69
|
+
if False, suppress load errors and leave `self._brill` unset when loading fails.
|
|
70
|
+
"""
|
|
71
|
+
try:
|
|
72
|
+
from brill_postaggers import BrillPostagger
|
|
73
|
+
self._brill = BrillPostagger.from_pretrained("pt")
|
|
74
|
+
except Exception as e:
|
|
75
|
+
if strict:
|
|
76
|
+
raise e
|
|
77
|
+
|
|
78
|
+
def tag(self, sentence: str) -> List[Tuple[str, str]]:
|
|
79
|
+
"""
|
|
80
|
+
Tags a sentence using the configured engine.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
sentence (str): The Portuguese text to tag.
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
List[Tuple[str, str]]: A list of (word, tag) tuples.
|
|
87
|
+
"""
|
|
88
|
+
engines = {
|
|
89
|
+
"auto": self.tag_auto,
|
|
90
|
+
"dummy": self.tag_dummy,
|
|
91
|
+
"brill": self.tag_brill,
|
|
92
|
+
"spacy": self.tag_spacy,
|
|
93
|
+
"lexicon": self.tag_lexicon
|
|
94
|
+
}
|
|
95
|
+
handler = engines.get(self.engine)
|
|
96
|
+
if not handler:
|
|
97
|
+
raise ValueError(f"Invalid engine: '{self.engine}'")
|
|
98
|
+
return handler(sentence)
|
|
99
|
+
|
|
100
|
+
def tag_auto(self, sentence: str) -> List[Tuple[str, str]]:
|
|
101
|
+
"""
|
|
102
|
+
Tag a sentence by trying spaCy then Brill and using the dummy tagger if both fail.
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
List[Tuple[str, str]]: A list of (word, POS-tag) tuples produced by the first successful tagger; if both spaCy and Brill raise exceptions, returns the dummy tagger's output.
|
|
106
|
+
"""
|
|
107
|
+
for method in [self.tag_brill, self.tag_spacy, self.tag_lexicon]:
|
|
108
|
+
try:
|
|
109
|
+
return method(sentence)
|
|
110
|
+
except:
|
|
111
|
+
continue
|
|
112
|
+
return self.tag_dummy(sentence)
|
|
113
|
+
|
|
114
|
+
def tag_brill(self, sentence: str) -> List[Tuple[str, str]]:
|
|
115
|
+
"""
|
|
116
|
+
Tag a sentence using the Brill-style POS tagger.
|
|
117
|
+
|
|
118
|
+
If the Brill tagger is not yet loaded, it will be initialized automatically.
|
|
119
|
+
|
|
120
|
+
Parameters:
|
|
121
|
+
sentence (str): Raw text sentence to be tagged.
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
List[Tuple[str, str]]: List of (token, POS-tag) pairs for the input sentence.
|
|
125
|
+
"""
|
|
126
|
+
if self._brill is None:
|
|
127
|
+
self.load_brill(strict=True)
|
|
128
|
+
return self._brill.tag(sentence)
|
|
129
|
+
|
|
130
|
+
def tag_spacy(self, sentence: str) -> List[Tuple[str, str]]:
|
|
131
|
+
"""
|
|
132
|
+
Tag a sentence using the configured spaCy Portuguese model.
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
List[Tuple[str, str]]: A list of (token_text, POS_tag) tuples, one per token. `POS_tag` is spaCy's coarse-grained part-of-speech label.
|
|
136
|
+
"""
|
|
137
|
+
if self._spacy is None:
|
|
138
|
+
self.load_spacy(strict=True)
|
|
139
|
+
doc = self._spacy(sentence)
|
|
140
|
+
return [(tok.text, tok.pos_) for tok in doc]
|
|
141
|
+
|
|
142
|
+
def tag_lexicon(self, sentence: str) -> List[Tuple[str, str]]:
|
|
143
|
+
tagged = []
|
|
144
|
+
|
|
145
|
+
# TODO: improve this
|
|
146
|
+
tokenize = lambda k: k.lower().replace(".", " .").replace("!", " !").replace("?", " ?").split()
|
|
147
|
+
|
|
148
|
+
for word in tokenize(sentence):
|
|
149
|
+
if word in self._lexicon.possible_postags:
|
|
150
|
+
possibilities = self._lexicon.possible_postags[word]
|
|
151
|
+
# TODO: how to choose postag? use surrounding context
|
|
152
|
+
tagged.append((word, possibilities[0]))
|
|
153
|
+
else:
|
|
154
|
+
tagged.append((word, self._guess_pos(word)))
|
|
155
|
+
return tagged
|
|
156
|
+
|
|
157
|
+
@classmethod
|
|
158
|
+
def tag_dummy(cls, sentence: str) -> List[Tuple[str, str]]:
|
|
159
|
+
"""
|
|
160
|
+
Split the sentence on whitespace and tag each token as the noun "NOUN".
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
List[Tuple[str, str]]: A list of (word, tag) tuples where each whitespace-separated token from the input is paired with the tag "NOUN".
|
|
164
|
+
"""
|
|
165
|
+
return [(word, cls._guess_pos(word)) for word in sentence.split()]
|
|
166
|
+
|
|
167
|
+
@staticmethod
|
|
168
|
+
def _guess_pos(word: str) -> str:
|
|
169
|
+
"""Applies heuristic rules to guess the POS tag."""
|
|
170
|
+
lower_word = word.lower()
|
|
171
|
+
|
|
172
|
+
# Rule 0: Punctuation
|
|
173
|
+
if not word.isalnum():
|
|
174
|
+
return "PUNCT"
|
|
175
|
+
|
|
176
|
+
# Rule 0.5: Numbers
|
|
177
|
+
if word.isdigit():
|
|
178
|
+
return "NUM"
|
|
179
|
+
|
|
180
|
+
# 1. Closed-class words (Functional words that rarely change category)
|
|
181
|
+
COMMON_WORDS = {
|
|
182
|
+
# Articles
|
|
183
|
+
"o": "DET", "a": "DET", "os": "DET", "as": "DET", "um": "DET", "uma": "DET",
|
|
184
|
+
# Prepositions
|
|
185
|
+
"de": "ADP", "do": "ADP", "da": "ADP", "em": "ADP", "no": "ADP", "na": "ADP",
|
|
186
|
+
"por": "ADP", "para": "ADP", "com": "ADP", "sem": "ADP", #"a": "ADP",
|
|
187
|
+
# Conjunctions
|
|
188
|
+
"e": "CCONJ", "mas": "CCONJ", "ou": "CCONJ", "que": "SCONJ", "se": "SCONJ",
|
|
189
|
+
# Pronouns
|
|
190
|
+
"eu": "PRON", "ele": "PRON", "ela": "PRON", "nós": "PRON", "eles": "PRON",
|
|
191
|
+
"isso": "PRON", "aquilo": "PRON",
|
|
192
|
+
# Common Verbs (Auxiliary/Copula)
|
|
193
|
+
"é": "AUX", "foi": "AUX", "são": "AUX", "está": "AUX", "ser": "AUX", "ter": "AUX",
|
|
194
|
+
# Adverbs
|
|
195
|
+
"não": "ADV", "sim": "ADV", "muito": "ADV", "mais": "ADV"
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
# 2. Suffix Rules (Order matters: check longer suffixes first)
|
|
199
|
+
SUFFIX_RULES = [
|
|
200
|
+
("mente", "ADV"), # rapidamente
|
|
201
|
+
("ando", "VERB"), # cantando
|
|
202
|
+
("endo", "VERB"), # correndo
|
|
203
|
+
("indo", "VERB"), # partindo
|
|
204
|
+
("aram", "VERB"), # cantaram
|
|
205
|
+
("eram", "VERB"), # correram
|
|
206
|
+
("iram", "VERB"), # partiram
|
|
207
|
+
("ava", "VERB"), # cantava
|
|
208
|
+
("ria", "VERB"), # cantaria
|
|
209
|
+
("dor", "NOUN"), # jogador
|
|
210
|
+
("ção", "NOUN"), # ação
|
|
211
|
+
("são", "NOUN"), # tensão
|
|
212
|
+
("dade", "NOUN"), # cidade
|
|
213
|
+
("ismo", "NOUN"), # realismo
|
|
214
|
+
("ista", "NOUN"), # realista
|
|
215
|
+
("oso", "ADJ"), # formoso
|
|
216
|
+
("osa", "ADJ"), # formosa
|
|
217
|
+
("vel", "ADJ"), # amável
|
|
218
|
+
("al", "ADJ"), # nacional
|
|
219
|
+
("ar", "VERB"), # amar
|
|
220
|
+
("er", "VERB"), # comer
|
|
221
|
+
("ir", "VERB"), # partir (careful with 'ir' the verb itself)
|
|
222
|
+
]
|
|
223
|
+
|
|
224
|
+
# Rule 1: Dictionary Lookup (O(1) speed)
|
|
225
|
+
if lower_word in COMMON_WORDS:
|
|
226
|
+
return COMMON_WORDS[lower_word]
|
|
227
|
+
|
|
228
|
+
# Rule 2: Suffix Morphology
|
|
229
|
+
for suffix, tag in SUFFIX_RULES:
|
|
230
|
+
if lower_word.endswith(suffix):
|
|
231
|
+
# Exception: prevent very short words triggering rules (e.g. "lar" ending in "ar")
|
|
232
|
+
if len(lower_word) > len(suffix):
|
|
233
|
+
return tag
|
|
234
|
+
|
|
235
|
+
# Rule 3: Capitalization (Proper Noun heuristic)
|
|
236
|
+
# If it's not the start of the sentence (hard to know context-free here) but matches Title case
|
|
237
|
+
if word[0].isupper() and word[1:].islower():
|
|
238
|
+
return "PROPN"
|
|
239
|
+
|
|
240
|
+
# Rule 4: Fallback
|
|
241
|
+
return "NOUN"
|
|
242
|
+
|
|
243
|
+
if __name__ == "__main__":
|
|
244
|
+
# Initialize with 'auto' to use the best available engine
|
|
245
|
+
tagger = TugaTagger(engine="auto")
|
|
246
|
+
|
|
247
|
+
text = "O gato preto pulou o muro."
|
|
248
|
+
|
|
249
|
+
print(f"Using engine: {tagger.engine}")
|
|
250
|
+
tags = tagger.tag(text)
|
|
251
|
+
|
|
252
|
+
for word, pos in tags:
|
|
253
|
+
print(f"{word:10} -> {pos}")
|
tugatagger/version.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tugatagger
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: A unified wrapper for Portuguese POS tagging and benchmarking.
|
|
5
|
+
Home-page: https://github.com/TigreGotico/tugatagger
|
|
6
|
+
Author: JarbasAI
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
11
|
+
Classifier: Natural Language :: Portuguese
|
|
12
|
+
Requires-Python: >=3.7
|
|
13
|
+
Provides-Extra: spacy
|
|
14
|
+
Requires-Dist: spacy; extra == "spacy"
|
|
15
|
+
Provides-Extra: lexicon
|
|
16
|
+
Requires-Dist: tugalex; extra == "lexicon"
|
|
17
|
+
Provides-Extra: brill
|
|
18
|
+
Requires-Dist: brill_postagger; extra == "brill"
|
|
19
|
+
Dynamic: author
|
|
20
|
+
Dynamic: classifier
|
|
21
|
+
Dynamic: home-page
|
|
22
|
+
Dynamic: provides-extra
|
|
23
|
+
Dynamic: requires-python
|
|
24
|
+
Dynamic: summary
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
tugatagger/__init__.py,sha256=LhfLuYaVgwcKuWbQL160nn5JUf4ORIep4wUecSTu_tw,9736
|
|
2
|
+
tugatagger/version.py,sha256=BBHAnuig_b4qYC9X5LlEdCAxhBIrCC_Q-L9LwjAN6RY,237
|
|
3
|
+
tugatagger-0.0.1.dist-info/METADATA,sha256=hdwh_2AQ_Hcw_zY1xWlPIT0ICFMPdDrFpUdCgTeUQfU,799
|
|
4
|
+
tugatagger-0.0.1.dist-info/WHEEL,sha256=SmOxYU7pzNKBqASvQJ7DjX3XGUF92lrGhMb3R6_iiqI,91
|
|
5
|
+
tugatagger-0.0.1.dist-info/top_level.txt,sha256=nk1oSKN2LnoAsUq8iUtudTGu6b5ppV59eTQyMz303fk,11
|
|
6
|
+
tugatagger-0.0.1.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
tugatagger
|