nini-datacomp 0.1.0__py3-none-win_amd64.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.
@@ -0,0 +1,55 @@
1
+ """nini-datacomp: comparação de tabelas em escala, com kernels C++ sobre as colunas do Polars.
2
+
3
+ Reescrita do datacompolars::
4
+
5
+ import nini_datacomp as dc
6
+
7
+ result = dc.compare("esquerda/", "direita.parquet", on=["id"], abs_tol={"valor": 0.005}, memory_limit="2GB")
8
+ result.is_match, result.rows.mismatched, result.columns["valor"].mismatches
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from importlib import metadata
14
+
15
+ # antes de qualquer import do Polars: o alocador dele lê a configuração uma vez só, quando carrega
16
+ from .core.allocator import configure as _configure_allocator
17
+
18
+ ALLOCATOR_STATUS = _configure_allocator()
19
+
20
+ from .api import compare # noqa: E402
21
+ from .core.memory import pool_limit, pool_stats, release_memory # noqa: E402
22
+ from .errors import AllocatorWarning, NativeLibraryError, UnsupportedTypeError # noqa: E402
23
+ from .options import CompareOptions # noqa: E402
24
+ from .results import ( # noqa: E402
25
+ ColumnResult,
26
+ ColumnSummary,
27
+ ComparisonResult,
28
+ ExecutionInfo,
29
+ RowSummary,
30
+ TypeMismatch,
31
+ )
32
+
33
+ try:
34
+ __version__ = metadata.version("nini-datacomp")
35
+ except metadata.PackageNotFoundError: # pragma: no cover (rodando de src/ sem instalar)
36
+ __version__ = "0+desconhecida"
37
+
38
+ __all__ = [
39
+ "ALLOCATOR_STATUS",
40
+ "AllocatorWarning",
41
+ "ColumnResult",
42
+ "ColumnSummary",
43
+ "CompareOptions",
44
+ "ComparisonResult",
45
+ "ExecutionInfo",
46
+ "NativeLibraryError",
47
+ "RowSummary",
48
+ "TypeMismatch",
49
+ "UnsupportedTypeError",
50
+ "__version__",
51
+ "compare",
52
+ "pool_limit",
53
+ "pool_stats",
54
+ "release_memory",
55
+ ]
@@ -0,0 +1,121 @@
1
+ """Carrega a biblioteca C++ (libdatacomp) via ctypes e aplica as assinaturas de `_signatures.py`.
2
+
3
+ Ordem de busca:
4
+ 1. variável de ambiente ``NINI_DATACOMP_LIB`` (caminho do arquivo; útil para testar um build manual);
5
+ 2. a pasta do pacote (instalação normal por wheel);
6
+ 3. os arquivos registrados na instalação (instalação editável do ``uv sync``, em que o pacote aponta para ``src/`` e a
7
+ biblioteca compilada fica no site-packages).
8
+
9
+ O ctypes solta o GIL durante cada chamada, então os blocos rodam em paralelo de verdade numa ThreadPool.
10
+ Copiado do nini-datagen 0.3.0, sem a inicialização de tabelas (aqui não há tabelas a montar no carregamento).
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import ctypes
16
+ import os
17
+ import sys
18
+ import threading
19
+ from importlib import metadata
20
+ from pathlib import Path
21
+
22
+ import numpy as np
23
+
24
+ from ._signatures import ABI_VERSION, SIGNATURES
25
+ from .errors import NativeLibraryError
26
+
27
+ __all__ = ["ENV_VAR", "LIB_NAMES", "lib", "library_path", "ptr"]
28
+
29
+ ENV_VAR = "NINI_DATACOMP_LIB"
30
+ if sys.platform == "win32":
31
+ LIB_NAMES: tuple[str, ...] = ("datacomp.dll",)
32
+ elif sys.platform == "darwin":
33
+ LIB_NAMES = ("libdatacomp.dylib", "libdatacomp.so")
34
+ else:
35
+ LIB_NAMES = ("libdatacomp.so",)
36
+
37
+ _lock = threading.Lock()
38
+ _lib: ctypes.CDLL | None = None
39
+ _path: Path | None = None
40
+
41
+
42
+ def _candidates() -> list[Path]:
43
+ env = os.environ.get(ENV_VAR)
44
+ if env:
45
+ return [Path(env)] # pedido explícito: não procura em outro lugar
46
+ here = Path(__file__).resolve().parent
47
+ found = [here / name for name in LIB_NAMES]
48
+ try:
49
+ files = metadata.files("nini-datacomp") or []
50
+ except metadata.PackageNotFoundError:
51
+ files = []
52
+ found += [Path(str(f.locate())) for f in files if f.name in LIB_NAMES]
53
+ return found
54
+
55
+
56
+ def _load(path: Path) -> ctypes.CDLL:
57
+ try:
58
+ handle = ctypes.CDLL(str(path))
59
+ except OSError as e:
60
+ raise NativeLibraryError(f"não consegui carregar {path}: {e}") from e
61
+ try:
62
+ version = handle.dc_abi_version()
63
+ except AttributeError as e:
64
+ raise NativeLibraryError(f"{path} não é a biblioteca do nini-datacomp (falta dc_abi_version)") from e
65
+ if version != ABI_VERSION:
66
+ raise NativeLibraryError(
67
+ f"{path} tem ABI {version}, mas o pacote Python espera {ABI_VERSION}: recompile"
68
+ " (uv sync --reinstall-package nini-datacomp) ou reinstale o pacote"
69
+ )
70
+ missing = [name for name in SIGNATURES if not hasattr(handle, name)]
71
+ if missing:
72
+ raise NativeLibraryError(f"{path} não exporta {missing}")
73
+ for name, (restype, argtypes) in SIGNATURES.items():
74
+ fn = getattr(handle, name)
75
+ fn.restype = restype
76
+ fn.argtypes = argtypes
77
+ return handle
78
+
79
+
80
+ def lib() -> ctypes.CDLL:
81
+ """A biblioteca carregada (carrega na primeira chamada; seguro entre threads)."""
82
+ global _lib, _path
83
+ if _lib is not None:
84
+ return _lib
85
+ with _lock:
86
+ if _lib is None:
87
+ tried = []
88
+ for path in _candidates():
89
+ if path.is_file():
90
+ _lib, _path = _load(path), path
91
+ break
92
+ tried.append(str(path))
93
+ else:
94
+ raise NativeLibraryError(
95
+ "biblioteca C++ do nini-datacomp não encontrada (procurei em: "
96
+ + ", ".join(tried)
97
+ + "). Numa cópia do repositório, rode `uv sync`; para um build manual, aponte "
98
+ + f"{ENV_VAR} para o arquivo."
99
+ )
100
+ return _lib
101
+
102
+
103
+ def library_path() -> Path:
104
+ """Caminho do arquivo da biblioteca carregada."""
105
+ lib()
106
+ assert _path is not None
107
+ return _path
108
+
109
+
110
+ def _reset() -> None:
111
+ """Só para testes: esquece a biblioteca carregada (a próxima chamada a `lib()` procura de novo)."""
112
+ global _lib, _path
113
+ with _lock:
114
+ _lib, _path = None, None
115
+
116
+
117
+ def ptr(a: np.ndarray) -> int:
118
+ """Endereço do primeiro elemento de um array contíguo (o C++ escreve/lê nele como um vetor plano)."""
119
+ if not a.flags.c_contiguous:
120
+ raise ValueError("o array precisa ser contíguo (C order) para ir ao C++")
121
+ return a.ctypes.data
@@ -0,0 +1,27 @@
1
+ """Tipos dos argumentos de cada função da biblioteca C++ (GERADO por tools/gen_bindings.py; não edite)."""
2
+ from __future__ import annotations
3
+
4
+ from ctypes import c_double, c_float, c_int, c_int64, c_uint8, c_uint16, c_uint32, c_uint64, c_void_p
5
+
6
+ ABI_VERSION = 8
7
+
8
+ # nome: (tipo de retorno, [tipos dos argumentos])
9
+ SIGNATURES = {
10
+ "dc_abi_version": (c_int, []),
11
+ "dc_count_unset": (c_int64, [c_void_p, c_int64, c_int64]),
12
+ "dc_describe_batch": (c_int, [c_void_p, c_void_p, c_int, c_void_p]),
13
+ "dc_compare_column": (c_int64, [c_void_p, c_void_p, c_void_p, c_void_p, c_void_p, c_int64, c_int64, c_int, c_double, c_double, c_double, c_void_p]),
14
+ "dc_compare_rows": (c_int, [c_int, c_void_p, c_void_p, c_int64, c_int64, c_int64, c_void_p]),
15
+ "dc_compare_pairs": (c_int, [c_int, c_int, c_void_p, c_void_p, c_int, c_void_p, c_void_p, c_void_p, c_void_p, c_void_p, c_int64, c_int64, c_int64, c_void_p]),
16
+ "dc_align_keys": (c_int, [c_int, c_void_p, c_int64, c_void_p, c_int64, c_int, c_void_p, c_void_p, c_void_p]),
17
+ "dc_pool_alloc": (c_int64, [c_int64]),
18
+ "dc_pool_free": (None, [c_int64]),
19
+ "dc_pool_limit": (c_int64, [c_int64]),
20
+ "dc_pool_trim": (None, []),
21
+ "dc_pool_stats": (None, [c_void_p]),
22
+ "dc_parquet_row_groups": (c_int64, [c_void_p, c_int64, c_void_p, c_int64]),
23
+ "dc_row_hash": (c_int, [c_int, c_void_p, c_int64, c_int64, c_int, c_void_p]),
24
+ "dc_diff_pairs": (c_int64, [c_int, c_void_p, c_void_p, c_void_p, c_void_p, c_void_p, c_void_p, c_int64, c_int64, c_int, c_void_p, c_void_p]),
25
+ }
26
+
27
+ __all__ = ['ABI_VERSION', 'SIGNATURES', 'c_double', 'c_float', 'c_int', 'c_int64', 'c_uint8', 'c_uint16', 'c_uint32', 'c_uint64', 'c_void_p']
nini_datacomp/api.py ADDED
@@ -0,0 +1,250 @@
1
+ """`compare`: a função pública.
2
+
3
+ Abre as fontes, ajusta os nomes das colunas, confere a chave (os erros fatais vão para ``result.errors``, como no
4
+ datacompolars) e roda o motor (`pipeline._run`): janelas que cabem no ``memory_limit``, hash de linha nas colunas
5
+ exatas, coluna a coluna nas com tolerância, contagens por coluna e amostras.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import time
11
+ from collections.abc import Sequence
12
+ from typing import Any
13
+
14
+ import polars as pl
15
+
16
+ from .io import Source, SourceLike, open_source
17
+ from .options import CompareOptions
18
+ from .pipeline import RowCounts, _run, _tol_of, align_columns, sample_key_names
19
+ from .results import ColumnResult, ColumnSummary, ComparisonResult, ExecutionInfo, RowSummary, TypeMismatch
20
+
21
+ __all__ = ["compare"]
22
+
23
+
24
+ def compare(
25
+ left: SourceLike,
26
+ right: SourceLike,
27
+ on: str | Sequence[str],
28
+ *,
29
+ options: CompareOptions | None = None,
30
+ **kwargs: Any,
31
+ ) -> ComparisonResult:
32
+ """Compara ``left`` e ``right`` (caminho de Parquet, pasta, glob, ``pl.DataFrame`` ou ``pl.LazyFrame``) pela chave
33
+ ``on`` (uma coluna ou várias).
34
+
35
+ As opções vêm em ``options`` (`CompareOptions`) ou como argumentos nomeados (``abs_tol=0.01``); com os dois, os
36
+ argumentos nomeados ganham. Erros fatais (chave que falta, de tipos diferentes, repetida) não levantam exceção:
37
+ ficam em ``result.errors``, com ``result.aborted`` verdadeiro. Tipos que o pacote não compara (List, Struct)
38
+ levantam `UnsupportedTypeError`.
39
+ """
40
+ t0 = time.perf_counter()
41
+ if kwargs:
42
+ base = options.model_dump() if options is not None else {}
43
+ options = CompareOptions(**{**base, **kwargs})
44
+ opts = options if options is not None else CompareOptions()
45
+ lower = not opts.case_sensitive_columns
46
+ keys = _keys(on, lower=lower)
47
+ abs_tol, rel_tol = _names_of(opts.abs_tol, lower), _names_of(opts.rel_tol, lower)
48
+
49
+ ls, rs = open_source(left), open_source(right)
50
+ errors: list[str] = []
51
+ if lower:
52
+ ls, rs = _lowercase(ls, "left", errors), _lowercase(rs, "right", errors)
53
+ if errors:
54
+ return _failed(opts, keys, errors, left_only=(), right_only=(), t0=t0)
55
+ lschema, rschema = ls.lazy.collect_schema(), rs.lazy.collect_schema()
56
+ left_only = tuple(c for c in lschema.names() if c not in rschema)
57
+ right_only = tuple(c for c in rschema.names() if c not in lschema)
58
+ errors += _key_errors(lschema, rschema, keys, opts.cast)
59
+ if errors:
60
+ return _failed(opts, keys, errors, left_only=left_only, right_only=right_only, t0=t0)
61
+
62
+ cols = align_columns(ls, rs, keys, opts.cast) # schemas só; UnsupportedTypeError para aninhadas
63
+ warnings: list[str] = []
64
+ if not cols.compared:
65
+ warnings.append("No common columns besides the join key: only row membership was compared.")
66
+ for label, tol in (("abs_tol", abs_tol), ("rel_tol", rel_tol)):
67
+ if isinstance(tol, dict) and (unknown := sorted(set(tol) - set(cols.compared))):
68
+ warnings.append(f"{label} names columns that are not compared: {unknown}")
69
+
70
+ res = _run(
71
+ left, right, keys, memory_limit=opts.memory_limit, window_rows=opts.window_rows, columns=None, prefetch=True,
72
+ engine="auto", hashing=True, casting=opts.cast, verify_hash=opts.verify_hash, threads=opts.threads,
73
+ abs_tol=abs_tol, rel_tol=rel_tol, ignore_case=opts.ignore_case, ignore_spaces=opts.ignore_whitespace,
74
+ details=opts.details, samples=opts.samples, sources=(ls, rs),
75
+ ) # fmt: skip
76
+ if res.dup_left:
77
+ errors.append(f"Duplicate join keys in the left table: {res.dup_left:,} surplus rows")
78
+ if res.dup_right:
79
+ errors.append(f"Duplicate join keys in the right table: {res.dup_right:,} surplus rows")
80
+
81
+ dtypes = {c: cols.left_casts.get(c, lschema[c]) for c in lschema.names()}
82
+ key_dtypes = [_sample_dtype(dtypes[k]) for k in keys]
83
+ details = opts.details and not errors
84
+ results = {}
85
+ for c in res.compared_columns:
86
+ n = res.column_mismatches.get(c, 0) if details else None
87
+ tol = (_tol_of(abs_tol, c), _tol_of(rel_tol, c)) if c in res.tolerance_columns else None
88
+ results[c] = ColumnResult(
89
+ name=c,
90
+ dtype=str(dtypes[c]),
91
+ mismatches=n,
92
+ rate=None if n is None else (n / res.common if res.common else 0.0),
93
+ samples=_samples(
94
+ res, c, keys=keys, key_dtypes=key_dtypes, dtype=_sample_dtype(dtypes[c]), names=opts.names
95
+ ),
96
+ tolerance=tol,
97
+ )
98
+ columns = ColumnSummary(
99
+ join_columns=tuple(keys),
100
+ results=results,
101
+ left_only=left_only,
102
+ right_only=right_only,
103
+ type_mismatches=tuple(TypeMismatch(c, a, b) for c, a, b in res.type_mismatches),
104
+ )
105
+ return ComparisonResult(
106
+ names=opts.names,
107
+ join_columns=tuple(keys),
108
+ rows=_rows(res, aborted=bool(errors)),
109
+ columns=columns,
110
+ errors=tuple(errors),
111
+ warnings=tuple(warnings),
112
+ execution=_execution(res, opts, details=details, t0=t0),
113
+ )
114
+
115
+
116
+ # ----------------------------------------------------------------------------- nomes e chave
117
+ def _keys(on: str | Sequence[str], *, lower: bool) -> list[str]:
118
+ keys = [on] if isinstance(on, str) else list(on)
119
+ if not keys:
120
+ raise ValueError("on precisa de ao menos uma coluna")
121
+ if any(not isinstance(k, str) or not k.strip() for k in keys):
122
+ raise ValueError("on não pode ter nomes vazios")
123
+ if lower:
124
+ keys = [k.lower() for k in keys]
125
+ if len(set(keys)) != len(keys):
126
+ raise ValueError(f"on tem colunas repetidas: {keys}")
127
+ return keys
128
+
129
+
130
+ def _names_of(tol: float | dict[str, float], lower: bool) -> float | dict[str, float]:
131
+ if isinstance(tol, dict) and lower:
132
+ return {k.lower(): v for k, v in tol.items()}
133
+ return tol
134
+
135
+
136
+ def _lowercase(src: Source, side: str, errors: list[str]) -> Source:
137
+ """Os nomes das colunas em minúsculas; dois que viram o mesmo são erro fatal (como no datacompolars), e a fonte
138
+ volta como estava (renomear com a colisão quebraria no Polars)."""
139
+ names = src.lazy.collect_schema().names()
140
+ seen: set[str] = set()
141
+ clash = False
142
+ for n in names:
143
+ low = n.lower()
144
+ if low in seen:
145
+ errors.append(f"Columns collide in the {side} table after lowercasing names: '{low}'")
146
+ clash = True
147
+ seen.add(low)
148
+ return src if clash else src.rename({n: n.lower() for n in names if n != n.lower()})
149
+
150
+
151
+ def _key_errors(lschema: pl.Schema, rschema: pl.Schema, keys: Sequence[str], cast: str) -> list[str]:
152
+ errors = []
153
+ for side, schema in (("left", lschema), ("right", rschema)):
154
+ missing = [k for k in keys if k not in schema]
155
+ if missing:
156
+ errors.append(f"Join keys missing from the {side} table: {missing}")
157
+ if errors or cast != "none":
158
+ return errors
159
+ return [
160
+ f"Join key '{k}' has different types: left {lschema[k]} vs right {rschema[k]}"
161
+ for k in keys
162
+ if lschema[k] != rschema[k]
163
+ ]
164
+
165
+
166
+ # ----------------------------------------------------------------------------- resultado
167
+ def _sample_dtype(dtype: pl.DataType) -> pl.DataType:
168
+ """O tipo da coluna nas amostras: Categorical e Enum são comparados (e mostrados) como texto."""
169
+ return pl.String() if isinstance(dtype, (pl.Categorical, pl.Enum)) else dtype
170
+
171
+
172
+ def _samples(
173
+ res: RowCounts,
174
+ column: str,
175
+ *,
176
+ keys: Sequence[str],
177
+ key_dtypes: Sequence[pl.DataType],
178
+ dtype: pl.DataType,
179
+ names: tuple[str, str],
180
+ ) -> pl.DataFrame:
181
+ headers = [*keys, f"{column} ({names[0]})", f"{column} ({names[1]})"]
182
+ frame = res.samples.get(column)
183
+ if frame is None:
184
+ return pl.DataFrame(schema=dict(zip(headers, [*key_dtypes, dtype, dtype], strict=True)))
185
+ raw = [*sample_key_names(len(keys)), "__left", "__right"]
186
+ return frame.rename(dict(zip(raw, headers, strict=True)))
187
+
188
+
189
+ def _rows(res: RowCounts, *, aborted: bool) -> RowSummary:
190
+ if aborted: # como no datacompolars: nada contado
191
+ return RowSummary(0, 0, 0, 0, 0, 0)
192
+ return RowSummary(
193
+ left_total=res.left_total,
194
+ right_total=res.right_total,
195
+ common=res.common,
196
+ left_only=res.left_only,
197
+ right_only=res.right_only,
198
+ mismatched=res.mismatched or 0,
199
+ )
200
+
201
+
202
+ def _versions() -> tuple[str, str]:
203
+ from . import __version__ # noqa: PLC0415 - o pacote já carregou quando compare roda
204
+
205
+ return __version__, pl.__version__
206
+
207
+
208
+ def _execution(res: RowCounts, opts: CompareOptions, *, details: bool, t0: float) -> ExecutionInfo:
209
+ version, polars_version = _versions()
210
+ return ExecutionInfo(
211
+ windows=res.windows,
212
+ rows_per_window=res.rows_per_window,
213
+ column_groups=res.column_groups,
214
+ memory_limit=res.memory_limit,
215
+ window_bytes=res.window_bytes,
216
+ tolerance_columns=res.tolerance_columns,
217
+ verify_hash=opts.verify_hash,
218
+ hash_collisions=res.hash_collisions,
219
+ details_computed=details,
220
+ version=version,
221
+ polars_version=polars_version,
222
+ total_seconds=round(time.perf_counter() - t0, 6),
223
+ timings=dict(res.timings),
224
+ )
225
+
226
+
227
+ def _failed(
228
+ opts: CompareOptions,
229
+ keys: Sequence[str],
230
+ errors: Sequence[str],
231
+ *,
232
+ left_only: tuple[str, ...],
233
+ right_only: tuple[str, ...],
234
+ t0: float,
235
+ ) -> ComparisonResult:
236
+ """O resultado de uma comparação que não começou (erro fatal antes de ler os dados)."""
237
+ version, polars_version = _versions()
238
+ return ComparisonResult(
239
+ names=opts.names,
240
+ join_columns=tuple(keys),
241
+ rows=RowSummary(0, 0, 0, 0, 0, 0),
242
+ columns=ColumnSummary(join_columns=tuple(keys), results={}, left_only=left_only, right_only=right_only),
243
+ errors=tuple(errors),
244
+ execution=ExecutionInfo(
245
+ verify_hash=opts.verify_hash,
246
+ version=version,
247
+ polars_version=polars_version,
248
+ total_seconds=round(time.perf_counter() - t0, 6),
249
+ ),
250
+ )
@@ -0,0 +1 @@
1
+ """Infraestrutura interna: threads (`blocks`), memória (`memory`) e a interface C do Arrow (`arrow_c`, `arrow`)."""
@@ -0,0 +1,60 @@
1
+ """O alocador do Polars: no Windows ele é o mimalloc, que por padrão guarda a memória liberada em vez de devolvê-la.
2
+
3
+ Medido na fase 1c (``tools/diag_memory.py``, 10M linhas, ``memory_limit = 1GB``): com as janelas de ~200 MB, o
4
+ processo ia acumulando memória janela após janela e terminava com ~1,1 GB, quase toda já liberada pelo Polars. Com
5
+ ``MIMALLOC_PURGE_DELAY=0`` o mimalloc devolve a memória na hora: pico de ~0,6–0,7 GB (−40 a −48%), sem custo de tempo
6
+ nas tabelas largas (10M x 50: 5,6 s → 4,2 s) e +0,3 s nas estreitas (10M x 10).
7
+
8
+ O mimalloc lê a variável uma vez, quando a biblioteca do Polars carrega; depois não há como mudar. Por isso o
9
+ ``nini_datacomp`` a define no próprio import, antes de importar o Polars, e só se o Polars ainda não estiver carregado
10
+ (e sem sobrescrever um valor que já exista). Se o Polars veio antes, avisa com `AllocatorWarning`.
11
+
12
+ ``NINI_DATACOMP_ALLOCATOR=0`` desliga tudo isso (nem define a variável, nem avisa).
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import os
18
+ import sys
19
+ import warnings
20
+ from collections.abc import Container, MutableMapping
21
+ from typing import Literal
22
+
23
+ from ..errors import AllocatorWarning
24
+
25
+ __all__ = ["ALLOCATOR_ENV", "SETTINGS", "Status", "configure"]
26
+
27
+ ALLOCATOR_ENV = "NINI_DATACOMP_ALLOCATOR"
28
+ SETTINGS = {"MIMALLOC_PURGE_DELAY": "0", "MIMALLOC_RESET_DELAY": "0"} # RESET_DELAY: o nome antigo, mimalloc < 2.1
29
+
30
+ # "set": definida agora; "user": já estava definida; "late": o Polars já tinha carregado; "disabled": desligado
31
+ Status = Literal["set", "user", "late", "disabled"]
32
+
33
+ _LATE = (
34
+ "o Polars foi importado antes do nini_datacomp, então o alocador dele (mimalloc) vai guardar a memória que libera "
35
+ "e o pico de uma comparação grande pode ficar bem maior (medido: até ~1,9x). Para evitar, importe o nini_datacomp "
36
+ "antes do Polars ou defina MIMALLOC_PURGE_DELAY=0 antes de iniciar o Python. NINI_DATACOMP_ALLOCATOR=0 silencia "
37
+ "este aviso."
38
+ )
39
+
40
+
41
+ def configure(
42
+ environ: MutableMapping[str, str] | None = None,
43
+ modules: Container[str] | None = None,
44
+ platform: str | None = None,
45
+ ) -> Status:
46
+ """Define as variáveis do mimalloc se ainda der tempo; avisa (só no Windows, onde foi medido) se não der."""
47
+ environ = os.environ if environ is None else environ
48
+ modules = sys.modules if modules is None else modules
49
+ platform = sys.platform if platform is None else platform
50
+ if environ.get(ALLOCATOR_ENV, "1") == "0":
51
+ return "disabled"
52
+ if "MIMALLOC_PURGE_DELAY" in environ:
53
+ return "user"
54
+ if "polars" in modules:
55
+ if platform == "win32":
56
+ warnings.warn(_LATE, AllocatorWarning, stacklevel=2)
57
+ return "late"
58
+ for name, value in SETTINGS.items():
59
+ environ.setdefault(name, value)
60
+ return "set"