agrobr 0.1.0__tar.gz → 0.1.2__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.
- {agrobr-0.1.0 → agrobr-0.1.2}/PKG-INFO +1 -1
- {agrobr-0.1.0 → agrobr-0.1.2}/agrobr/cache/policies.py +104 -17
- {agrobr-0.1.0 → agrobr-0.1.2}/agrobr/cepea/client.py +1 -1
- {agrobr-0.1.0 → agrobr-0.1.2}/pyproject.toml +1 -1
- {agrobr-0.1.0 → agrobr-0.1.2}/.github/workflows/health_check.yml +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/.github/workflows/publish.yml +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/.github/workflows/structure_monitor.yml +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/.github/workflows/tests.yml +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/.gitignore +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/.pre-commit-config.yaml +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/.structures/baseline.json +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/CHANGELOG.md +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/CODE_OF_CONDUCT.md +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/CONTRIBUTING.md +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/LICENSE +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/README.md +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/agrobr/__init__.py +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/agrobr/alerts/__init__.py +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/agrobr/alerts/notifier.py +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/agrobr/cache/__init__.py +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/agrobr/cache/duckdb_store.py +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/agrobr/cache/history.py +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/agrobr/cache/migrations.py +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/agrobr/cepea/__init__.py +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/agrobr/cepea/api.py +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/agrobr/cepea/parsers/__init__.py +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/agrobr/cepea/parsers/base.py +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/agrobr/cepea/parsers/consensus.py +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/agrobr/cepea/parsers/detector.py +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/agrobr/cepea/parsers/fingerprint.py +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/agrobr/cepea/parsers/v1.py +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/agrobr/cli.py +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/agrobr/conab/__init__.py +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/agrobr/conab/api.py +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/agrobr/conab/client.py +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/agrobr/conab/parsers/__init__.py +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/agrobr/conab/parsers/v1.py +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/agrobr/constants.py +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/agrobr/exceptions.py +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/agrobr/health/__init__.py +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/agrobr/health/checker.py +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/agrobr/health/reporter.py +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/agrobr/http/__init__.py +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/agrobr/http/browser.py +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/agrobr/http/rate_limiter.py +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/agrobr/http/retry.py +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/agrobr/http/user_agents.py +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/agrobr/ibge/__init__.py +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/agrobr/ibge/api.py +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/agrobr/ibge/client.py +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/agrobr/models.py +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/agrobr/normalize/__init__.py +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/agrobr/normalize/dates.py +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/agrobr/normalize/encoding.py +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/agrobr/normalize/regions.py +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/agrobr/normalize/units.py +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/agrobr/noticias_agricolas/__init__.py +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/agrobr/noticias_agricolas/client.py +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/agrobr/noticias_agricolas/parser.py +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/agrobr/sync.py +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/agrobr/telemetry/__init__.py +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/agrobr/telemetry/collector.py +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/agrobr/utils/__init__.py +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/agrobr/utils/logging.py +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/agrobr/validators/__init__.py +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/agrobr/validators/sanity.py +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/agrobr/validators/structural.py +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/docs/advanced/resilience.md +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/docs/advanced/troubleshooting.md +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/docs/api/cepea.md +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/docs/api/conab.md +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/docs/api/ibge.md +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/docs/index.md +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/docs/quickstart.md +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/examples/analise_soja.py +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/examples/pipeline_async.py +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/mkdocs.yml +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/scripts/alert_structure_change.py +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/scripts/compare_structures.py +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/scripts/create_workflows.py +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/scripts/fetch_structures.py +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/scripts/update_golden.py +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/tests/__init__.py +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/tests/conftest.py +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/tests/golden_data/cepea/soja_sample/expected.json +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/tests/golden_data/cepea/soja_sample/metadata.json +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/tests/golden_data/cepea/soja_sample/response.html +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/tests/golden_data/conab/safra_sample/expected.json +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/tests/golden_data/conab/safra_sample/metadata.json +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/tests/golden_data/conab/safra_sample/response.xlsx +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/tests/test_cepea/__init__.py +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/tests/test_cepea/test_api.py +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/tests/test_cepea/test_fingerprint.py +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/tests/test_cepea/test_parser.py +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/tests/test_conab/__init__.py +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/tests/test_conab/test_parser.py +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/tests/test_golden.py +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/tests/test_ibge/__init__.py +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/tests/test_ibge/test_api.py +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/tests/test_ibge/test_client.py +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/tests/test_noticias_agricolas/__init__.py +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/tests/test_noticias_agricolas/test_parser.py +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/tests/test_validators/__init__.py +0 -0
- {agrobr-0.1.0 → agrobr-0.1.2}/tests/test_validators/test_sanity.py +0 -0
|
@@ -1,10 +1,6 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Políticas de cache e TTL por fonte.
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
1
|
from __future__ import annotations
|
|
6
2
|
|
|
7
|
-
from datetime import datetime, timedelta
|
|
3
|
+
from datetime import datetime, time, timedelta
|
|
8
4
|
from enum import Enum
|
|
9
5
|
from typing import NamedTuple
|
|
10
6
|
|
|
@@ -17,6 +13,7 @@ class CachePolicy(NamedTuple):
|
|
|
17
13
|
ttl_seconds: int
|
|
18
14
|
stale_max_seconds: int
|
|
19
15
|
description: str
|
|
16
|
+
smart_expiry: bool = False # Se True, usa horário fixo de expiração
|
|
20
17
|
|
|
21
18
|
|
|
22
19
|
class TTL(Enum):
|
|
@@ -33,41 +30,53 @@ class TTL(Enum):
|
|
|
33
30
|
DAYS_90 = 90 * 24 * 60 * 60
|
|
34
31
|
|
|
35
32
|
|
|
33
|
+
# Horário de atualização do CEPEA (18:00 Brasília)
|
|
34
|
+
CEPEA_UPDATE_HOUR = 18
|
|
35
|
+
CEPEA_UPDATE_MINUTE = 0
|
|
36
|
+
|
|
37
|
+
|
|
36
38
|
POLICIES: dict[str, CachePolicy] = {
|
|
37
39
|
"cepea_diario": CachePolicy(
|
|
38
|
-
ttl_seconds=TTL.
|
|
40
|
+
ttl_seconds=TTL.HOURS_24.value, # Fallback se smart_expiry falhar
|
|
39
41
|
stale_max_seconds=TTL.HOURS_24.value * 2,
|
|
40
|
-
description="CEPEA indicador diário (
|
|
42
|
+
description="CEPEA indicador diário (expira às 18h)",
|
|
43
|
+
smart_expiry=True,
|
|
41
44
|
),
|
|
42
45
|
"cepea_semanal": CachePolicy(
|
|
43
46
|
ttl_seconds=TTL.HOURS_24.value,
|
|
44
47
|
stale_max_seconds=TTL.DAYS_7.value,
|
|
45
48
|
description="CEPEA indicador semanal (atualiza sexta)",
|
|
49
|
+
smart_expiry=False,
|
|
46
50
|
),
|
|
47
51
|
"conab_safras": CachePolicy(
|
|
48
52
|
ttl_seconds=TTL.HOURS_24.value,
|
|
49
53
|
stale_max_seconds=TTL.DAYS_30.value,
|
|
50
54
|
description="CONAB safras (atualiza mensalmente)",
|
|
55
|
+
smart_expiry=False,
|
|
51
56
|
),
|
|
52
57
|
"conab_balanco": CachePolicy(
|
|
53
58
|
ttl_seconds=TTL.HOURS_24.value,
|
|
54
59
|
stale_max_seconds=TTL.DAYS_30.value,
|
|
55
60
|
description="CONAB balanço (atualiza mensalmente)",
|
|
61
|
+
smart_expiry=False,
|
|
56
62
|
),
|
|
57
63
|
"ibge_pam": CachePolicy(
|
|
58
64
|
ttl_seconds=TTL.DAYS_7.value,
|
|
59
65
|
stale_max_seconds=TTL.DAYS_90.value,
|
|
60
66
|
description="IBGE PAM (atualiza anualmente)",
|
|
67
|
+
smart_expiry=False,
|
|
61
68
|
),
|
|
62
69
|
"ibge_lspa": CachePolicy(
|
|
63
70
|
ttl_seconds=TTL.HOURS_24.value,
|
|
64
71
|
stale_max_seconds=TTL.DAYS_30.value,
|
|
65
72
|
description="IBGE LSPA (atualiza mensalmente)",
|
|
73
|
+
smart_expiry=False,
|
|
66
74
|
),
|
|
67
75
|
"noticias_agricolas": CachePolicy(
|
|
68
|
-
ttl_seconds=TTL.
|
|
76
|
+
ttl_seconds=TTL.HOURS_24.value, # Fallback
|
|
69
77
|
stale_max_seconds=TTL.HOURS_24.value * 2,
|
|
70
|
-
description="Notícias Agrícolas (mirror CEPEA)",
|
|
78
|
+
description="Notícias Agrícolas (expira às 18h, mirror CEPEA)",
|
|
79
|
+
smart_expiry=True,
|
|
71
80
|
),
|
|
72
81
|
}
|
|
73
82
|
|
|
@@ -106,6 +115,37 @@ def get_policy(source: Fonte | str, endpoint: str | None = None) -> CachePolicy:
|
|
|
106
115
|
return POLICIES[default_key]
|
|
107
116
|
|
|
108
117
|
|
|
118
|
+
def _get_smart_expiry_time() -> datetime:
|
|
119
|
+
"""
|
|
120
|
+
Calcula próximo horário de expiração para CEPEA (18h).
|
|
121
|
+
|
|
122
|
+
CEPEA atualiza dados por volta das 17-18h.
|
|
123
|
+
Cache expira às 18h para pegar dados novos.
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
Datetime da próxima expiração
|
|
127
|
+
"""
|
|
128
|
+
now = datetime.now()
|
|
129
|
+
today_expiry = datetime.combine(now.date(), time(CEPEA_UPDATE_HOUR, CEPEA_UPDATE_MINUTE))
|
|
130
|
+
|
|
131
|
+
if now < today_expiry:
|
|
132
|
+
# Ainda não chegou às 18h hoje → expira hoje às 18h
|
|
133
|
+
return today_expiry
|
|
134
|
+
else:
|
|
135
|
+
# Já passou das 18h → expira amanhã às 18h
|
|
136
|
+
return today_expiry + timedelta(days=1)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _get_last_expiry_time() -> datetime:
|
|
140
|
+
"""
|
|
141
|
+
Retorna o último horário de expiração (18h anterior).
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
Datetime da última expiração
|
|
145
|
+
"""
|
|
146
|
+
return _get_smart_expiry_time() - timedelta(days=1)
|
|
147
|
+
|
|
148
|
+
|
|
109
149
|
def get_ttl(source: Fonte | str, endpoint: str | None = None) -> int:
|
|
110
150
|
"""
|
|
111
151
|
Retorna TTL em segundos para uma fonte.
|
|
@@ -134,20 +174,31 @@ def get_stale_max(source: Fonte | str, endpoint: str | None = None) -> int:
|
|
|
134
174
|
return get_policy(source, endpoint).stale_max_seconds
|
|
135
175
|
|
|
136
176
|
|
|
137
|
-
def is_expired(created_at: datetime, source: Fonte | str) -> bool:
|
|
177
|
+
def is_expired(created_at: datetime, source: Fonte | str, endpoint: str | None = None) -> bool:
|
|
138
178
|
"""
|
|
139
179
|
Verifica se entrada de cache está expirada.
|
|
140
180
|
|
|
181
|
+
Para fontes com smart_expiry (CEPEA), expira às 18h.
|
|
182
|
+
Para outras fontes, usa TTL fixo.
|
|
183
|
+
|
|
141
184
|
Args:
|
|
142
185
|
created_at: Data de criação
|
|
143
186
|
source: Fonte de dados
|
|
187
|
+
endpoint: Endpoint específico
|
|
144
188
|
|
|
145
189
|
Returns:
|
|
146
190
|
True se expirado
|
|
147
191
|
"""
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
192
|
+
policy = get_policy(source, endpoint)
|
|
193
|
+
|
|
194
|
+
if policy.smart_expiry:
|
|
195
|
+
# Smart expiry: cache válido se criado após última expiração (18h)
|
|
196
|
+
last_expiry = _get_last_expiry_time()
|
|
197
|
+
return created_at < last_expiry
|
|
198
|
+
|
|
199
|
+
# TTL fixo tradicional
|
|
200
|
+
expires_at = created_at + timedelta(seconds=policy.ttl_seconds)
|
|
201
|
+
return datetime.now() > expires_at
|
|
151
202
|
|
|
152
203
|
|
|
153
204
|
def is_stale_acceptable(created_at: datetime, source: Fonte | str) -> bool:
|
|
@@ -163,13 +214,16 @@ def is_stale_acceptable(created_at: datetime, source: Fonte | str) -> bool:
|
|
|
163
214
|
"""
|
|
164
215
|
stale_max = get_stale_max(source)
|
|
165
216
|
max_acceptable = created_at + timedelta(seconds=stale_max)
|
|
166
|
-
return datetime.
|
|
217
|
+
return datetime.now() <= max_acceptable
|
|
167
218
|
|
|
168
219
|
|
|
169
220
|
def calculate_expiry(source: Fonte | str, endpoint: str | None = None) -> datetime:
|
|
170
221
|
"""
|
|
171
222
|
Calcula data de expiração para nova entrada.
|
|
172
223
|
|
|
224
|
+
Para fontes com smart_expiry (CEPEA), retorna próximas 18h.
|
|
225
|
+
Para outras fontes, usa TTL fixo.
|
|
226
|
+
|
|
173
227
|
Args:
|
|
174
228
|
source: Fonte de dados
|
|
175
229
|
endpoint: Endpoint específico
|
|
@@ -177,8 +231,12 @@ def calculate_expiry(source: Fonte | str, endpoint: str | None = None) -> dateti
|
|
|
177
231
|
Returns:
|
|
178
232
|
Data de expiração
|
|
179
233
|
"""
|
|
180
|
-
|
|
181
|
-
|
|
234
|
+
policy = get_policy(source, endpoint)
|
|
235
|
+
|
|
236
|
+
if policy.smart_expiry:
|
|
237
|
+
return _get_smart_expiry_time()
|
|
238
|
+
|
|
239
|
+
return datetime.now() + timedelta(seconds=policy.ttl_seconds)
|
|
182
240
|
|
|
183
241
|
|
|
184
242
|
class InvalidationReason(Enum):
|
|
@@ -196,6 +254,7 @@ def should_refresh(
|
|
|
196
254
|
created_at: datetime,
|
|
197
255
|
source: Fonte | str,
|
|
198
256
|
force: bool = False,
|
|
257
|
+
endpoint: str | None = None,
|
|
199
258
|
) -> tuple[bool, str]:
|
|
200
259
|
"""
|
|
201
260
|
Determina se cache deve ser atualizado.
|
|
@@ -204,6 +263,7 @@ def should_refresh(
|
|
|
204
263
|
created_at: Data de criação do cache
|
|
205
264
|
source: Fonte de dados
|
|
206
265
|
force: Forçar atualização
|
|
266
|
+
endpoint: Endpoint específico
|
|
207
267
|
|
|
208
268
|
Returns:
|
|
209
269
|
Tupla (deve_atualizar, razão)
|
|
@@ -211,7 +271,7 @@ def should_refresh(
|
|
|
211
271
|
if force:
|
|
212
272
|
return True, "force_refresh"
|
|
213
273
|
|
|
214
|
-
if is_expired(created_at, source):
|
|
274
|
+
if is_expired(created_at, source, endpoint):
|
|
215
275
|
return True, "expired"
|
|
216
276
|
|
|
217
277
|
return False, "fresh"
|
|
@@ -238,3 +298,30 @@ def format_ttl(seconds: int) -> str:
|
|
|
238
298
|
|
|
239
299
|
days = seconds // 86400
|
|
240
300
|
return f"{days} dia{'s' if days > 1 else ''}"
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def get_next_update_info(source: Fonte | str) -> dict[str, str]:
|
|
304
|
+
"""
|
|
305
|
+
Retorna informações sobre próxima atualização.
|
|
306
|
+
|
|
307
|
+
Args:
|
|
308
|
+
source: Fonte de dados
|
|
309
|
+
|
|
310
|
+
Returns:
|
|
311
|
+
Dict com info de expiração
|
|
312
|
+
"""
|
|
313
|
+
policy = get_policy(source)
|
|
314
|
+
|
|
315
|
+
if policy.smart_expiry:
|
|
316
|
+
next_expiry = _get_smart_expiry_time()
|
|
317
|
+
return {
|
|
318
|
+
"type": "smart",
|
|
319
|
+
"expires_at": next_expiry.strftime("%Y-%m-%d %H:%M"),
|
|
320
|
+
"description": f"Expira às {CEPEA_UPDATE_HOUR}h (atualização CEPEA)",
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
return {
|
|
324
|
+
"type": "ttl",
|
|
325
|
+
"ttl": format_ttl(policy.ttl_seconds),
|
|
326
|
+
"description": policy.description,
|
|
327
|
+
}
|
|
@@ -15,7 +15,7 @@ from agrobr.normalize.encoding import decode_content
|
|
|
15
15
|
logger = structlog.get_logger()
|
|
16
16
|
|
|
17
17
|
# Flag para controlar uso de browser
|
|
18
|
-
_use_browser: bool =
|
|
18
|
+
_use_browser: bool = False
|
|
19
19
|
# Flag para controlar uso de fonte alternativa (Notícias Agrícolas)
|
|
20
20
|
_use_alternative_source: bool = True
|
|
21
21
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|