services.csv_writer
Módulo csv_writer.py
[PT-BR]
Função utilitária para gravar listas de objetos Pokemon
em arquivos CSV.
O módulo aplica uma limpeza básica nos dados, removendo quebras de linha e
espaços em excesso, além de pular linhas vazias ou inválidas.
Também registra no log a quantidade de registros exportados e quaisquer linhas ignoradas por estarem praticamente vazias.
[EN]
Utility function to write lists of Pokemon
objects to CSV files.
The module applies basic data cleaning, removing line breaks and
extra spaces, and skips empty or invalid rows.
It also logs the number of exported records and any skipped entries due to being nearly empty.
Uso típico / Typical usage: from services.csv_writer import write_pokemon_csv
written = write_pokemon_csv(pokemon_list, "output/pokemons.csv")
print(f"{written} Pokémon saved.")
1""" 2Módulo csv_writer.py 3===================== 4 5[PT-BR] 6Função utilitária para gravar listas de objetos ``Pokemon`` em arquivos CSV. 7O módulo aplica uma limpeza básica nos dados, removendo quebras de linha e 8espaços em excesso, além de pular linhas vazias ou inválidas. 9 10Também registra no log a quantidade de registros exportados e quaisquer 11linhas ignoradas por estarem praticamente vazias. 12 13[EN] 14Utility function to write lists of ``Pokemon`` objects to CSV files. 15The module applies basic data cleaning, removing line breaks and 16extra spaces, and skips empty or invalid rows. 17 18It also logs the number of exported records and any skipped entries 19due to being nearly empty. 20 21Uso típico / Typical usage: 22 from services.csv_writer import write_pokemon_csv 23 24 written = write_pokemon_csv(pokemon_list, "output/pokemons.csv") 25 print(f"{written} Pokémon saved.") 26""" 27import csv 28import logging 29from typing import Iterable, Protocol, runtime_checkable 30from pathlib import Path 31from contextlib import suppress 32 33@runtime_checkable 34class HasToDict(Protocol): 35 def to_dict(self) -> dict[str, object]: ... 36 37def clean_csv_value(value: object) -> object: 38 """ 39 [PT-BR] Limpa valores para escrita em CSV (quebras de linha, espaços). 40 [EN] Cleans values for CSV output (line breaks, extra spaces). 41 """ 42 if isinstance(value, str): 43 return value.replace("\n", " ").strip() 44 return value 45 46def is_effectively_empty(row: dict[str, object]) -> bool: 47 """ 48 [PT-BR] Verifica se a linha está vazia (ignora apenas None ou strings vazias). 49 [EN] Checks if the row is effectively empty (ignores None or empty strings). 50 """ 51 return all(v in (None, "") for v in row.values()) 52 53def write_pokemon_csv(pokemons: Iterable[HasToDict], path: str | Path, skip_empty: bool = True) -> int: 54 """ 55 [PT-BR] Grava objetos `Pokemon` no CSV, com limpeza básica e validação de linhas. 56 [EN] Writes `Pokemon` objects to CSV, with basic cleaning and row validation. 57 58 Parâmetros: 59 pokemons (Iterable[HasToDict]): lista de objetos com .to_dict(). 60 path (str | Path): caminho do arquivo de saída. 61 skip_empty (bool): se True, ignora linhas consideradas vazias. 62 63 Retorna: 64 int: quantidade de linhas efetivamente gravadas. 65 """ 66 pokemons = list(pokemons) 67 if not pokemons: 68 logging.warning("Empty Pokémon list: nothing to write.") 69 return 0 70 71 try: 72 fieldnames = sorted({k for p in pokemons for k in p.to_dict().keys()}) 73 written = 0 74 75 with open(path, "w", encoding="utf-8", newline="") as csvfile: 76 writer = csv.DictWriter(csvfile, fieldnames=fieldnames) 77 writer.writeheader() 78 79 for p in pokemons: 80 row = {k: clean_csv_value(v) for k, v in p.to_dict().items()} 81 82 if skip_empty and is_effectively_empty(row): 83 logging.warning("Row skipped—effectively empty: %s", row) 84 continue 85 86 writer.writerow(row) 87 written += 1 88 89 logging.info("%d Pokémon exported to '%s'.", written, path) 90 return written 91 92 except (IOError, OSError) as e: 93 logging.error("Failed to write CSV file '%s': %s", path, str(e), exc_info=True) 94 return 0
Base class for protocol classes.
Protocol classes are defined as::
class Proto(Protocol):
def meth(self) -> int:
...
Such classes are primarily used with static type checkers that recognize structural subtyping (static duck-typing), for example::
class C:
def meth(self) -> int:
return 0
def func(x: Proto) -> int:
return x.meth()
func(C()) # Passes static type check
See PEP 544 for details. Protocol classes decorated with @typing.runtime_checkable act as simple-minded runtime protocols that check only the presence of given attributes, ignoring their type signatures. Protocol classes can be generic, they are defined as::
class GenProto(Protocol[T]):
def meth(self) -> T:
...
1431def _no_init_or_replace_init(self, *args, **kwargs): 1432 cls = type(self) 1433 1434 if cls._is_protocol: 1435 raise TypeError('Protocols cannot be instantiated') 1436 1437 # Already using a custom `__init__`. No need to calculate correct 1438 # `__init__` to call. This can lead to RecursionError. See bpo-45121. 1439 if cls.__init__ is not _no_init_or_replace_init: 1440 return 1441 1442 # Initially, `__init__` of a protocol subclass is set to `_no_init_or_replace_init`. 1443 # The first instantiation of the subclass will call `_no_init_or_replace_init` which 1444 # searches for a proper new `__init__` in the MRO. The new `__init__` 1445 # replaces the subclass' old `__init__` (ie `_no_init_or_replace_init`). Subsequent 1446 # instantiation of the protocol subclass will thus use the new 1447 # `__init__` and no longer call `_no_init_or_replace_init`. 1448 for base in cls.__mro__: 1449 init = base.__dict__.get('__init__', _no_init_or_replace_init) 1450 if init is not _no_init_or_replace_init: 1451 cls.__init__ = init 1452 break 1453 else: 1454 # should not happen 1455 cls.__init__ = object.__init__ 1456 1457 cls.__init__(self, *args, **kwargs)
38def clean_csv_value(value: object) -> object: 39 """ 40 [PT-BR] Limpa valores para escrita em CSV (quebras de linha, espaços). 41 [EN] Cleans values for CSV output (line breaks, extra spaces). 42 """ 43 if isinstance(value, str): 44 return value.replace("\n", " ").strip() 45 return value
[PT-BR] Limpa valores para escrita em CSV (quebras de linha, espaços). [EN] Cleans values for CSV output (line breaks, extra spaces).
47def is_effectively_empty(row: dict[str, object]) -> bool: 48 """ 49 [PT-BR] Verifica se a linha está vazia (ignora apenas None ou strings vazias). 50 [EN] Checks if the row is effectively empty (ignores None or empty strings). 51 """ 52 return all(v in (None, "") for v in row.values())
[PT-BR] Verifica se a linha está vazia (ignora apenas None ou strings vazias). [EN] Checks if the row is effectively empty (ignores None or empty strings).
54def write_pokemon_csv(pokemons: Iterable[HasToDict], path: str | Path, skip_empty: bool = True) -> int: 55 """ 56 [PT-BR] Grava objetos `Pokemon` no CSV, com limpeza básica e validação de linhas. 57 [EN] Writes `Pokemon` objects to CSV, with basic cleaning and row validation. 58 59 Parâmetros: 60 pokemons (Iterable[HasToDict]): lista de objetos com .to_dict(). 61 path (str | Path): caminho do arquivo de saída. 62 skip_empty (bool): se True, ignora linhas consideradas vazias. 63 64 Retorna: 65 int: quantidade de linhas efetivamente gravadas. 66 """ 67 pokemons = list(pokemons) 68 if not pokemons: 69 logging.warning("Empty Pokémon list: nothing to write.") 70 return 0 71 72 try: 73 fieldnames = sorted({k for p in pokemons for k in p.to_dict().keys()}) 74 written = 0 75 76 with open(path, "w", encoding="utf-8", newline="") as csvfile: 77 writer = csv.DictWriter(csvfile, fieldnames=fieldnames) 78 writer.writeheader() 79 80 for p in pokemons: 81 row = {k: clean_csv_value(v) for k, v in p.to_dict().items()} 82 83 if skip_empty and is_effectively_empty(row): 84 logging.warning("Row skipped—effectively empty: %s", row) 85 continue 86 87 writer.writerow(row) 88 written += 1 89 90 logging.info("%d Pokémon exported to '%s'.", written, path) 91 return written 92 93 except (IOError, OSError) as e: 94 logging.error("Failed to write CSV file '%s': %s", path, str(e), exc_info=True) 95 return 0
[PT-BR] Grava objetos Pokemon
no CSV, com limpeza básica e validação de linhas.
[EN] Writes Pokemon
objects to CSV, with basic cleaning and row validation.
Parâmetros: pokemons (Iterable[HasToDict]): lista de objetos com .to_dict(). path (str | Path): caminho do arquivo de saída. skip_empty (bool): se True, ignora linhas consideradas vazias.
Retorna: int: quantidade de linhas efetivamente gravadas.