Skip to content

Commit ba19711

Browse files
fix(articlemeta): corrige memory leak por lru_cache em instância e robustez dos formatters journal/issue (#1424)
* refactor(journal/articlemeta_format): elimina lru_cache, melhora robustez e legibilidade - Substitui @lru_cache por cache manual em _titles_in_database_medline_secs para evitar memory leak (lru_cache em método de instância retém self indefinidamente no cache do decorador) - Renomeia _medline_titles para _titles_in_database_medline_secs para consistência com o nome da property - Renomeia _former_dict_journal_history para _format_journal_history_entry (corrige typo e segue convenção _format_* da classe) - Renomeia variável key_to_issn para key_to_value (nem todos os valores são ISSNs) - Adiciona guard clause 'journal_acron.upper() if journal_acron else None' em v930 para evitar AttributeError quando journal_acron é None - Refatora _format_publisher_info: substitui loop com break por .first() para obter apenas o primeiro owner, eliminando iteração desnecessária - Refatora _format_contact_address_info: substitui try/except genérico por checagem explícita 'if address', removendo tratamento silencioso de exceções - Refatora _format_journal_history: inverte condicional para early return, unifica blocos ADMITTED/INTERRUPTED duplicados em um único 'if in', e garante que subfield_b é determinado por evento (não acumulado) - Refatora _format_indexing_info: substitui duas list comprehensions separadas por um único loop para classificar medline vs secs, evitando dupla iteração sobre titles_in_db - Refatora _format_collection_info: remove checagem redundante 'if collection' (já coberta pelo 'if self.scielo_journal.collection') - Remove guard desnecessário 'if self.official' em _format_issn_list (método só é chamado dentro de bloco que já verifica self.official) - Adiciona TODO/docstring em _format_issn_type sinalizando possível inversão na lógica de negócio (issn_print == issn_scielo retorna 'ONLIN', o que parece invertido) - Corrige docstring typo 'Title Journalal' -> 'Title Journal' - Remove comentários óbvios/redundantes (e.g. 'tem que ser objeto datetime', 'Deixa preparado para tornar obsoleto') - Normaliza trailing whitespace e vírgulas finais em dicts/listas * refactor(issue/articlemeta_format): elimina lru_cache, corrige queryset e robustez - Substitui @lru_cache por cache manual em medline_titles para evitar memory leak (mesmo padrão aplicado no journal formatter) - Substitui atributo self.article (queryset avaliado no __init__) por property lazy self.article_qs que só executa a query quando acessado, evitando query desnecessária quando o dado não é utilizado - Corrige _format_article_info: usa self.article_qs (filtrado por issue + journal) em vez de self.obj.article_set (sem filtro por journal), garantindo contagem consistente com o queryset da classe - Adiciona guard clause em _format_field_use_system para checar journal_acron antes de chamar .upper(), e usa 'or empty string' para volume/number None, evitando concatenação com None - Refatora _format_issn_info com early return (guard clause) para reduzir nível de indentação; adiciona placeholder v435 no add_multiple_to_result para documentar que será preenchido por _format_issn_with_type - Adiciona guard 'if issns' antes de atribuir v435 em _format_issn_with_type para evitar lista vazia no resultado - Melhora select_related em _format_code_sections: inclui 'journal_toc' além de 'journal_toc__language'; adiciona guard 'if not journal_toc: continue' para pular registros sem relação - Remove comentários redundantes (e.g. 'Path to base issue', 'Ordem de publicação', 'Só adiciona v/n se houver') - Remove import não utilizado 'from functools import lru_cache' - Corrige missing newline at end of file
1 parent de9e01d commit ba19711

File tree

2 files changed

+169
-125
lines changed

2 files changed

+169
-125
lines changed

issue/formats/articlemeta_format.py

Lines changed: 64 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
from collections import defaultdict
2-
from functools import lru_cache
32

43
from article.models import Article
54
from core.utils.articlemeta_dict_utils import (
@@ -10,6 +9,7 @@
109
from journal.formats.articlemeta_format import ArticlemetaJournalFormatter
1110
from journal.models import SciELOJournal, TitleInDatabase
1211

12+
1313
def get_issue_type(issue):
1414
if issue.supplement:
1515
return "supplement"
@@ -32,7 +32,16 @@ def __init__(self, obj, collection):
3232
self.journal = self.obj.journal
3333
self._scielo_journal = None
3434
self._medline_titles = None
35-
self.article = Article.objects.filter(issue=self.obj, journal=self.journal)
35+
self._article_qs = None
36+
37+
@property
38+
def article_qs(self):
39+
"""Lazy queryset — só executa query quando necessário."""
40+
if self._article_qs is None:
41+
self._article_qs = Article.objects.filter(
42+
issue=self.obj, journal=self.journal
43+
)
44+
return self._article_qs
3645

3746
@property
3847
def scielo_journal(self):
@@ -49,13 +58,15 @@ def scielo_journal(self):
4958
return self._scielo_journal
5059

5160
@property
52-
@lru_cache(maxsize=1)
5361
def medline_titles(self):
54-
return list(
55-
TitleInDatabase.objects.filter(
56-
journal=self.journal, indexed_at__acronym__iexact="medline"
62+
"""Cache manual em vez de lru_cache (evita memory leak em instância)."""
63+
if self._medline_titles is None:
64+
self._medline_titles = list(
65+
TitleInDatabase.objects.filter(
66+
journal=self.journal, indexed_at__acronym__iexact="medline"
67+
)
5768
)
58-
)
69+
return self._medline_titles
5970

6071
def format(self):
6172
"""Formata todos os dados do issue"""
@@ -85,8 +96,6 @@ def format(self):
8596

8697
def _format_basic_info(self):
8798
"""Informações básicas do issue"""
88-
# Path to base issue
89-
9099
add_multiple_to_result(
91100
{
92101
"v31": self.obj.volume,
@@ -100,7 +109,6 @@ def _format_basic_info(self):
100109
},
101110
self.result["issue"],
102111
)
103-
# "v6": Ordem de publicação dos fascículos para apresentação na interface
104112

105113
if hasattr(self.obj, "issue_title"):
106114
items = [item.title for item in self.obj.issue_title.all() if item.title]
@@ -214,7 +222,6 @@ def _format_title_in_database(self):
214222

215223
def _format_metadata(self):
216224
"""Metadados e relacionamentos"""
217-
# tem que ser objeto datetime
218225
processing_date = self.obj.updated.strftime("%Y-%m-%d")
219226

220227
key_to_code = {
@@ -252,8 +259,12 @@ def _format_register_order_info(self):
252259

253260
def _format_field_use_system(self):
254261
"""Campo usado no sistema"""
255-
if self.scielo_journal:
256-
field_value = f"{self.scielo_journal.journal_acron.upper()}{self.obj.volume}{self.obj.number}"
262+
if self.scielo_journal and self.scielo_journal.journal_acron:
263+
field_value = (
264+
f"{self.scielo_journal.journal_acron.upper()}"
265+
f"{self.obj.volume or ''}"
266+
f"{self.obj.number or ''}"
267+
)
257268
add_to_result("v888", field_value, self.result["issue"])
258269

259270
def _format_legend_bibliographic(self):
@@ -278,10 +289,8 @@ def _format_legend_bibliographic(self):
278289
"a": self.obj.year,
279290
"_": "",
280291
}
281-
# Só adiciona 'v' se houver volume
282292
if self.obj.volume:
283293
entry["v"] = f"vol.{self.obj.volume}"
284-
# Só adiciona 'n' se houver number
285294
if self.obj.number:
286295
entry["n"] = f"no.{self.obj.number}"
287296
if self.obj.season:
@@ -308,28 +317,37 @@ def _format_title_summary(self):
308317
]
309318

310319
def _format_article_info(self):
311-
"""Informações de artigo"""
312-
if self.article.exists():
313-
article_count = str(self.obj.article_set.count())
320+
"""Informações de artigo — usa o queryset consistente (filtrado por issue + journal)."""
321+
if self.article_qs.exists():
322+
article_count = str(self.article_qs.count())
314323
add_to_result("v122", article_count, self.result["issue"])
315324

316325
def _format_issn_info(self):
317-
"""Informações de edição"""
318-
if self.scielo_journal and self.scielo_journal.journal and self.scielo_journal.journal.official:
319-
issn_print = self.scielo_journal.journal.official.issn_print
320-
issn_electronic = self.scielo_journal.journal.official.issn_electronic
321-
issn_scielo = self.scielo_journal.issn_scielo
322-
add_multiple_to_result(
323-
{
324-
"v35": issn_scielo,
325-
"v935": issn_electronic,
326-
},
327-
self.result["issue"],
328-
)
326+
"""Informações de ISSN"""
327+
if not (
328+
self.scielo_journal
329+
and self.scielo_journal.journal
330+
and self.scielo_journal.journal.official
331+
):
332+
return
333+
334+
official = self.scielo_journal.journal.official
335+
issn_print = official.issn_print
336+
issn_electronic = official.issn_electronic
337+
issn_scielo = self.scielo_journal.issn_scielo
338+
339+
add_multiple_to_result(
340+
{
341+
"v35": issn_scielo,
342+
"v435": None, # preenchido abaixo por _format_issn_with_type
343+
"v935": issn_electronic,
344+
},
345+
self.result["issue"],
346+
)
329347

330-
self._format_issn_with_type(issn_print, issn_electronic)
331-
self._format_issn_code_title(issn_print, issn_electronic)
332-
self._format_code(issn_scielo)
348+
self._format_issn_with_type(issn_print, issn_electronic)
349+
self._format_issn_code_title(issn_print, issn_electronic)
350+
self._format_code(issn_scielo)
333351

334352
def _format_code(self, issn_scielo):
335353
"""Informações de código"""
@@ -346,7 +364,8 @@ def _format_issn_with_type(self, issn_print, issn_electronic):
346364
issns.append({"_": issn_print, "t": "PRINT"})
347365
if issn_electronic:
348366
issns.append({"_": issn_electronic, "t": "ONLIN"})
349-
self.result["issue"]["v435"] = issns
367+
if issns:
368+
self.result["issue"]["v435"] = issns
350369

351370
def _format_issn_code_title(self, issn_print, issn_electronic):
352371
self.result["code_title"] = [
@@ -355,10 +374,18 @@ def _format_issn_code_title(self, issn_print, issn_electronic):
355374

356375
def _format_code_sections(self):
357376
data = []
358-
for toc in self.obj.table_of_contents.select_related("journal_toc__language").all():
377+
for toc in self.obj.table_of_contents.select_related(
378+
"journal_toc", "journal_toc__language"
379+
).all():
359380
journal_toc = toc.journal_toc
381+
if not journal_toc:
382+
continue
360383
code = getattr(journal_toc, "code", None)
361-
lang = getattr(journal_toc.language, "code2", None) if journal_toc.language else None
384+
lang = (
385+
getattr(journal_toc.language, "code2", None)
386+
if journal_toc.language
387+
else None
388+
)
362389
text = journal_toc.text
363390
if code:
364391
item = {"c": code, "_": ""}
@@ -379,4 +406,4 @@ def get_articlemeta_format_issue(obj, collection):
379406
data["title"] = ArticlemetaJournalFormatter(obj.journal, collection).format()
380407
formatter_issue = ArticlemetaIssueFormatter(obj, collection).format()
381408
data.update(formatter_issue)
382-
return data
409+
return data

0 commit comments

Comments
 (0)