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,10 @@
1
+ # START_VERSION_BLOCK
2
+ VERSION_MAJOR = 0
3
+ VERSION_MINOR = 0
4
+ VERSION_BUILD = 1
5
+ VERSION_ALPHA = 0
6
+ # END_VERSION_BLOCK
7
+
8
+ VERSION_STR = f"{VERSION_MAJOR}.{VERSION_MINOR}.{VERSION_BUILD}"
9
+ if VERSION_ALPHA:
10
+ VERSION_STR += f"a{VERSION_ALPHA}"
@@ -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,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (79.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ tugatagger