rust-py-cache 0.2.1__tar.gz → 0.2.3__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.
Files changed (32) hide show
  1. rust_py_cache-0.2.3/.github/workflows/ci.yml +71 -0
  2. rust_py_cache-0.2.3/.github/workflows/release.yml +256 -0
  3. rust_py_cache-0.2.3/CHANGELOG.md +109 -0
  4. {rust_py_cache-0.2.1 → rust_py_cache-0.2.3}/Cargo.lock +1 -1
  5. {rust_py_cache-0.2.1 → rust_py_cache-0.2.3}/Cargo.toml +21 -5
  6. rust_py_cache-0.2.3/LICENSE +21 -0
  7. rust_py_cache-0.2.3/Makefile +68 -0
  8. {rust_py_cache-0.2.1 → rust_py_cache-0.2.3}/PKG-INFO +6 -3
  9. rust_py_cache-0.2.3/clippy.toml +11 -0
  10. {rust_py_cache-0.2.1 → rust_py_cache-0.2.3}/pyproject.toml +18 -6
  11. {rust_py_cache-0.2.1 → rust_py_cache-0.2.3}/python/rust_py_cache/__init__.py +1 -1
  12. {rust_py_cache-0.2.1 → rust_py_cache-0.2.3}/src/cache.rs +5 -1
  13. {rust_py_cache-0.2.1 → rust_py_cache-0.2.3}/src/lib.rs +1 -6
  14. rust_py_cache-0.2.1/.github/workflows/ci.yml +0 -77
  15. rust_py_cache-0.2.1/.github/workflows/release.yml +0 -57
  16. rust_py_cache-0.2.1/CHANGELOG.md +0 -36
  17. {rust_py_cache-0.2.1 → rust_py_cache-0.2.3}/.gitignore +0 -0
  18. {rust_py_cache-0.2.1 → rust_py_cache-0.2.3}/PUBLISHING.md +0 -0
  19. {rust_py_cache-0.2.1 → rust_py_cache-0.2.3}/README.md +0 -0
  20. {rust_py_cache-0.2.1 → rust_py_cache-0.2.3}/ROADMAP.md +0 -0
  21. {rust_py_cache-0.2.1 → rust_py_cache-0.2.3}/examples/django_example.py +0 -0
  22. {rust_py_cache-0.2.1 → rust_py_cache-0.2.3}/examples/fastapi_example.py +0 -0
  23. {rust_py_cache-0.2.1 → rust_py_cache-0.2.3}/python/rust_py_cache/decorators.py +0 -0
  24. {rust_py_cache-0.2.1 → rust_py_cache-0.2.3}/src/entry.rs +0 -0
  25. {rust_py_cache-0.2.1 → rust_py_cache-0.2.3}/src/stats.rs +0 -0
  26. {rust_py_cache-0.2.1 → rust_py_cache-0.2.3}/src/sweeper.rs +0 -0
  27. {rust_py_cache-0.2.1 → rust_py_cache-0.2.3}/src/ttl.rs +0 -0
  28. {rust_py_cache-0.2.1 → rust_py_cache-0.2.3}/tests/test_cache_basic.py +0 -0
  29. {rust_py_cache-0.2.1 → rust_py_cache-0.2.3}/tests/test_delete.py +0 -0
  30. {rust_py_cache-0.2.1 → rust_py_cache-0.2.3}/tests/test_eviction.py +0 -0
  31. {rust_py_cache-0.2.1 → rust_py_cache-0.2.3}/tests/test_features.py +0 -0
  32. {rust_py_cache-0.2.1 → rust_py_cache-0.2.3}/tests/test_set_get.py +0 -0
@@ -0,0 +1,71 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ workflow_dispatch:
8
+
9
+ jobs:
10
+ # O lint faz parte do portão de release, não de um workflow paralelo.
11
+ # Nos repositórios de workflow único, um `lint` vermelho ao lado de uma
12
+ # publicação bem-sucedida seria ruído difícil de ler — a leitura passa a ser
13
+ # inequívoca: vermelho significa que não publicou.
14
+ #
15
+ # A toolchain é fixa, não "stable": com `-D warnings`, um clippy mais novo
16
+ # no runner reprova código que ninguém tocou, e a CI ficaria vermelha por
17
+ # conta do calendário. Subir este número vira uma mudança deliberada.
18
+ lint:
19
+ name: fmt + clippy
20
+ runs-on: ubuntu-latest
21
+ steps:
22
+ - uses: actions/checkout@v4
23
+ - uses: dtolnay/rust-toolchain@1.88.0
24
+ with:
25
+ components: rustfmt, clippy
26
+ - uses: Swatinem/rust-cache@v2
27
+ - name: cargo fmt
28
+ run: cargo fmt --all -- --check
29
+ # clippy só checa tipos, não linka, então --all-features é seguro aqui
30
+ # (diferente do `cargo test`, que precisa de --no-default-features).
31
+ - name: cargo clippy
32
+ run: cargo clippy --all-targets --all-features -- -D warnings
33
+
34
+ test:
35
+ name: cargo test + pytest
36
+ runs-on: ${{ matrix.os }}
37
+ strategy:
38
+ fail-fast: false
39
+ matrix:
40
+ os: [ubuntu-latest, macos-latest]
41
+ python-version: ["3.10", "3.12", "3.13"]
42
+ steps:
43
+ - uses: actions/checkout@v4
44
+
45
+ - name: Set up Python ${{ matrix.python-version }}
46
+ uses: actions/setup-python@v5
47
+ with:
48
+ python-version: ${{ matrix.python-version }}
49
+
50
+ - name: Set up Rust
51
+ uses: dtolnay/rust-toolchain@stable
52
+
53
+ - name: Cache cargo
54
+ uses: Swatinem/rust-cache@v2
55
+
56
+ # --no-default-features desliga extension-module, que manda o linker
57
+ # deixar os símbolos da libpython indefinidos. Isso é correto para a
58
+ # wheel, mas produz um binário de teste que não linka no Linux.
59
+ - name: cargo test
60
+ env:
61
+ PYO3_PYTHON: python
62
+ run: cargo test --no-default-features --lib
63
+
64
+ # `pip install` aciona o backend maturin, que compila a extensão e a
65
+ # instala no Python do runner — sem exigir virtualenv, ao contrário do
66
+ # `maturin develop`, que aborta quando não encontra um.
67
+ - name: Build, install the extension and run the Python tests
68
+ run: |
69
+ python -m pip install --upgrade pip
70
+ python -m pip install ".[dev]"
71
+ python -m pytest -q
@@ -0,0 +1,256 @@
1
+ name: Release
2
+
3
+ # Dispara ao publicar uma tag de versão (ex.: v0.1.0). Constrói as wheels para
4
+ # todas as plataformas + sdist e publica no PyPI via Trusted Publishing (OIDC),
5
+ # sem precisar guardar um token no repositório.
6
+ #
7
+ # O `ci.yml` roda em push/PR, mas é um workflow *separado*: um push de tag ia
8
+ # direto para build e publicação sem depender dele. O job `test` abaixo é o que
9
+ # torna a publicação condicionada a uma suíte verde — publicar no PyPI é
10
+ # irreversível, então não pode acontecer sobre código não verificado.
11
+ on:
12
+ push:
13
+ tags: ["v*"]
14
+ workflow_dispatch: {}
15
+
16
+ permissions:
17
+ contents: read
18
+
19
+ jobs:
20
+ # O lint faz parte do portão de release, não de um workflow paralelo.
21
+ # Nos repositórios de workflow único, um `lint` vermelho ao lado de uma
22
+ # publicação bem-sucedida seria ruído difícil de ler — a leitura passa a ser
23
+ # inequívoca: vermelho significa que não publicou.
24
+ #
25
+ # A toolchain é fixa, não "stable": com `-D warnings`, um clippy mais novo
26
+ # no runner reprova código que ninguém tocou, e a CI ficaria vermelha por
27
+ # conta do calendário. Subir este número vira uma mudança deliberada.
28
+ lint:
29
+ name: fmt + clippy
30
+ runs-on: ubuntu-latest
31
+ steps:
32
+ - uses: actions/checkout@v4
33
+ - uses: dtolnay/rust-toolchain@1.88.0
34
+ with:
35
+ components: rustfmt, clippy
36
+ - uses: Swatinem/rust-cache@v2
37
+ - name: cargo fmt
38
+ run: cargo fmt --all -- --check
39
+ # clippy só checa tipos, não linka, então --all-features é seguro aqui
40
+ # (diferente do `cargo test`, que precisa de --no-default-features).
41
+ - name: cargo clippy
42
+ run: cargo clippy --all-targets --all-features -- -D warnings
43
+
44
+ test:
45
+ name: Test (Rust + Python)
46
+ runs-on: ubuntu-latest
47
+ steps:
48
+ - uses: actions/checkout@v4
49
+ - uses: dtolnay/rust-toolchain@stable
50
+ - uses: actions/setup-python@v5
51
+ with:
52
+ python-version: "3.12"
53
+ - name: cargo test
54
+ env:
55
+ PYO3_PYTHON: python
56
+ run: cargo test --no-default-features --lib
57
+ - name: Build, install the extension and run the Python tests
58
+ run: |
59
+ python -m pip install --upgrade pip
60
+ python -m pip install ".[dev]"
61
+ python -m pytest -q
62
+
63
+ # A matriz abaixo substitui um job `build` que rodava em ubuntu/macos/windows
64
+ # com `maturin build` puro. Aquilo publicava 3 wheels e o de Linux herdava a
65
+ # glibc do runner, em vez de passar pelo container manylinux. Quem estava em
66
+ # aarch64, em musl ou em Mac Intel caía no sdist — que exige toolchain Rust.
67
+ linux:
68
+ runs-on: ubuntu-latest
69
+ if: startsWith(github.ref, 'refs/tags/')
70
+ needs: [test, lint]
71
+ strategy:
72
+ matrix:
73
+ target: [x86_64, aarch64]
74
+ steps:
75
+ - uses: actions/checkout@v4
76
+ - uses: PyO3/maturin-action@v1
77
+ with:
78
+ target: ${{ matrix.target }}
79
+ args: --release --out dist
80
+ # `auto` constrói dentro do container manylinux, então a wheel
81
+ # declara um piso de glibc portável em vez do da máquina de build.
82
+ manylinux: auto
83
+ - uses: actions/upload-artifact@v4
84
+ with:
85
+ name: wheels-linux-${{ matrix.target }}
86
+ path: dist
87
+
88
+ musllinux:
89
+ runs-on: ubuntu-latest
90
+ if: startsWith(github.ref, 'refs/tags/')
91
+ needs: [test, lint]
92
+ strategy:
93
+ matrix:
94
+ target: [x86_64, aarch64]
95
+ steps:
96
+ - uses: actions/checkout@v4
97
+ - uses: PyO3/maturin-action@v1
98
+ with:
99
+ target: ${{ matrix.target }}
100
+ args: --release --out dist
101
+ manylinux: musllinux_1_2
102
+ - uses: actions/upload-artifact@v4
103
+ with:
104
+ name: wheels-musllinux-${{ matrix.target }}
105
+ path: dist
106
+
107
+ windows:
108
+ runs-on: windows-latest
109
+ if: startsWith(github.ref, 'refs/tags/')
110
+ needs: [test, lint]
111
+ steps:
112
+ - uses: actions/checkout@v4
113
+ - uses: actions/setup-python@v5
114
+ with:
115
+ architecture: x64
116
+ - uses: PyO3/maturin-action@v1
117
+ with:
118
+ target: x64
119
+ args: --release --out dist
120
+ - uses: actions/upload-artifact@v4
121
+ with:
122
+ name: wheels-windows-x64
123
+ path: dist
124
+
125
+ macos:
126
+ runs-on: macos-latest
127
+ if: startsWith(github.ref, 'refs/tags/')
128
+ needs: [test, lint]
129
+ strategy:
130
+ matrix:
131
+ target: [x86_64, aarch64]
132
+ steps:
133
+ - uses: actions/checkout@v4
134
+ - uses: PyO3/maturin-action@v1
135
+ with:
136
+ target: ${{ matrix.target }}
137
+ args: --release --out dist
138
+ - uses: actions/upload-artifact@v4
139
+ with:
140
+ name: wheels-macos-${{ matrix.target }}
141
+ path: dist
142
+
143
+ sdist:
144
+ runs-on: ubuntu-latest
145
+ if: startsWith(github.ref, 'refs/tags/')
146
+ needs: [test, lint]
147
+ steps:
148
+ - uses: actions/checkout@v4
149
+ - uses: PyO3/maturin-action@v1
150
+ with:
151
+ command: sdist
152
+ args: --out dist
153
+ - uses: actions/upload-artifact@v4
154
+ with:
155
+ name: wheels-sdist
156
+ path: dist
157
+
158
+ publish:
159
+ name: publish to PyPI
160
+ # Guarda além do gatilho: sem ela, um `workflow_dispatch` a partir de um
161
+ # branch publicaria no PyPI.
162
+ if: startsWith(github.ref, 'refs/tags/')
163
+ needs: [lint, test, linux, musllinux, windows, macos, sdist]
164
+ runs-on: ubuntu-latest
165
+ environment: pypi
166
+ permissions:
167
+ id-token: write # necessário para o Trusted Publishing (OIDC)
168
+ steps:
169
+ - uses: actions/download-artifact@v4
170
+ with:
171
+ pattern: wheels-*
172
+ merge-multiple: true
173
+ path: dist
174
+ - uses: actions/setup-python@v5
175
+ with:
176
+ python-version: "3.12"
177
+
178
+ # Conferência antes de qualquer coisa sair do runner: publicar um
179
+ # conjunto incompleto é pior que não publicar, porque o PyPI nunca deixa
180
+ # reusar um nome de arquivo — uma versão meio-publicada só pode ser
181
+ # completada, nunca substituída.
182
+ - name: Verify the artifact set is complete
183
+ run: |
184
+ set -euo pipefail
185
+ ls -la dist/
186
+ test -n "$(find dist -name '*.tar.gz' -print -quit)" \
187
+ || { echo "::error::sdist ausente — abortando antes de publicar"; exit 1; }
188
+ wheels=$(find dist -name '*.whl' | wc -l)
189
+ echo "wheels: $wheels"
190
+ test "$wheels" -ge 7 \
191
+ || { echo "::error::só $wheels wheels; esperado >= 7 (2 linux, 2 musl, 1 win, 2 macos)"; exit 1; }
192
+
193
+ # O PyPI devolve 500 transitório com frequência suficiente para importar,
194
+ # e o upload é arquivo por arquivo: uma falha no meio deixa a versão
195
+ # publicada pela metade. `skip-existing` é o que torna a segunda
196
+ # tentativa segura — sem ele, ela morre nos arquivos que já subiram.
197
+ #
198
+ # São dois passos explícitos em vez de um wrapper de retry porque o
199
+ # trusted publishing (OIDC) exige a action oficial, que não dá para
200
+ # acionar por linha de comando.
201
+ - name: Publish to PyPI (attempt 1)
202
+ id: publish1
203
+ continue-on-error: true
204
+ uses: pypa/gh-action-pypi-publish@release/v1
205
+ with:
206
+ packages-dir: dist
207
+ skip-existing: true
208
+
209
+ - name: Wait before retrying
210
+ if: steps.publish1.outcome == 'failure'
211
+ run: sleep 45
212
+
213
+ - name: Publish to PyPI (attempt 2)
214
+ if: steps.publish1.outcome == 'failure'
215
+ uses: pypa/gh-action-pypi-publish@release/v1
216
+ with:
217
+ packages-dir: dist
218
+ skip-existing: true
219
+
220
+ # Prova que a release está completa, em vez de confiar num código de
221
+ # saída que uma publicação parcial também produz.
222
+ - name: Verify every file reached PyPI
223
+ run: |
224
+ set -euo pipefail
225
+ python -m pip install --quiet requests
226
+ python - <<'PY'
227
+ import os, sys, glob, time, requests
228
+ version = os.environ["GITHUB_REF_NAME"].lstrip("v")
229
+ # Só .whl e .tar.gz: são os únicos tipos de arquivo que o PyPI hospeda.
230
+ # `pypa/gh-action-pypi-publish` deixa um .publish.attestation por
231
+ # distribuição em dist/ depois de publicar (atestados PEP 740, que
232
+ # sobem junto do arquivo, não como arquivo próprio). Um glob amplo os
233
+ # conta como "faltando no PyPI" e reprova uma release que deu certo.
234
+ local = {
235
+ os.path.basename(p)
236
+ for pattern in ("dist/*.whl", "dist/*.tar.gz")
237
+ for p in glob.glob(pattern)
238
+ }
239
+ url = f"https://pypi.org/pypi/rust-py-cache/{version}/json"
240
+ for attempt in range(6): # a API JSON do PyPI é cacheada
241
+ response = requests.get(url, timeout=30)
242
+ if response.status_code == 200:
243
+ remote = {f["filename"] for f in response.json()["urls"]}
244
+ missing = local - remote
245
+ if not missing:
246
+ print(f"OK: {len(remote)} arquivos publicados na {version}")
247
+ sys.exit(0)
248
+ else:
249
+ missing = local
250
+ print(f"tentativa {attempt + 1}: faltam {len(missing)}")
251
+ time.sleep(20)
252
+ print("::error::faltaram arquivos no PyPI:")
253
+ for name in sorted(missing):
254
+ print(f" - {name}")
255
+ sys.exit(1)
256
+ PY
@@ -0,0 +1,109 @@
1
+ # Changelog
2
+
3
+ Todas as mudanças relevantes deste projeto são registradas aqui.
4
+
5
+ O formato segue [Keep a Changelog](https://keepachangelog.com/pt-BR/1.1.0/),
6
+ e o versionamento segue [SemVer](https://semver.org/lang/pt-BR/).
7
+
8
+ ## [0.2.3] — não publicada
9
+
10
+ Versão de empacotamento e qualidade: nada muda no comportamento da biblioteca.
11
+
12
+ ### Adicionado
13
+
14
+ - **`LICENSE`** (MIT). O projeto declarava MIT no `pyproject.toml` desde sempre,
15
+ mas o texto da licença não existia no repositório nem na wheel (**P1-4**).
16
+ - **`clippy.toml` e `Makefile`** compartilhados com os outros quatro `rust_py_*`,
17
+ e um job `lint` (`cargo fmt --check` + `cargo clippy -D warnings`) no `ci.yml`
18
+ e no `release.yml` — este barrando a publicação, como o `test` já barrava
19
+ (**P1-6**). O `cargo clippy` já estava limpo aqui; foi o único dos cinco.
20
+
21
+ ### Corrigido
22
+
23
+ - **Metadado de licença ausente no PyPI.** O `pyproject.toml` usava
24
+ `license = { text = "MIT" }`, forma legada que o Metadata 2.4 depreca. Passou
25
+ para a do PEP 639 (`license = "MIT"` + `license-files = ["LICENSE"]`), e o
26
+ classifier `License :: OSI Approved` saiu — ele é mutuamente exclusivo com
27
+ `License-Expression`. A 0.2.2 no PyPI mostra `license: MIT` no campo legado e
28
+ `license_expression: null`; a partir daqui é o contrário, que é o correto.
29
+
30
+ - **Piso da maturin subiu para 1.14** no `[build-system]`. Era `>=1.7`, e
31
+ versões anteriores não emitem `License-Expression` — foi essa, e não a forma
32
+ no `pyproject`, a causa raiz do metadado faltando.
33
+
34
+ ### Declarado como Cargo
35
+
36
+ - `Cargo.toml` não tinha campo `license` nenhum. Agora tem `license = "MIT"`,
37
+ como os outros quatro.
38
+
39
+ ## [0.2.2] - 2026-08-11
40
+
41
+ Release de infraestrutura: nada muda no comportamento da biblioteca, mas muda
42
+ quem consegue instalá-la sem compilar.
43
+
44
+ ### Corrigido
45
+
46
+ - **A publicação não roda mais sem teste.** O `ci.yml` roda em push/PR, mas é
47
+ um workflow **separado**: um push de tag ia direto para build e publicação
48
+ sem depender dele. O `release.yml` agora tem o seu próprio job `test` e todos
49
+ os demais declaram `needs: test`. O job `publish` também ganhou
50
+ `if: startsWith(github.ref, 'refs/tags/')` — sem essa guarda, um
51
+ `workflow_dispatch` a partir de um branch publicaria no PyPI.
52
+ (BACKLOG2 P1-1)
53
+ - **Cobertura de plataformas.** A 0.2.1 publicou 3 wheels — uma por runner
54
+ (ubuntu, macos, windows) — e a de Linux herdava a glibc da máquina de build
55
+ em vez de passar pelo container manylinux. Caíam no sdist, ou seja,
56
+ precisavam de toolchain Rust: Linux aarch64, Alpine/musl e Mac Intel. Agora
57
+ são 7 wheels (2 manylinux, 2 musllinux, 1 Windows, 2 macOS) mais o sdist.
58
+ - **O upload aguenta o 500 do PyPI.** O upload é arquivo por arquivo e sem
59
+ transação — uma falha no meio deixa a versão publicada pela metade, e o PyPI
60
+ nunca permite reusar um nome de arquivo. Agora há `skip-existing` (que é o
61
+ que torna a segunda tentativa segura), uma segunda tentativa explícita,
62
+ conferência do conjunto de artefatos **antes** de publicar e validação contra
63
+ a API do PyPI **depois**. (BACKLOG2 P1-9)
64
+
65
+ ### Alterado
66
+
67
+ - `crate-type` ganhou `rlib` além de `cdylib`, e `extension-module` virou
68
+ feature opcional ligada por padrão. Assim
69
+ `cargo test --no-default-features --lib` produz um binário de teste que linka
70
+ no Linux, em vez de depender da tolerância do linker do macOS. É o mesmo
71
+ arranjo dos outros quatro projetos. (BACKLOG2 P1-8)
72
+ - O `ci.yml` deixou de criar um virtualenv à mão para rodar `maturin develop`;
73
+ agora usa `pip install ".[dev]"`, que aciona o mesmo backend maturin sem
74
+ exigir virtualenv.
75
+
76
+ ### Adicionado
77
+
78
+ - Extra `[project.optional-dependencies] dev`. Este era o único dos cinco
79
+ projetos sem um. (BACKLOG2 P2-9)
80
+
81
+ ## [0.2.1] - 2026-08-11
82
+
83
+ ### Corrigido
84
+
85
+ - `@cache.cached` sobre função `async def` guardava no cache o objeto coroutine
86
+ em vez do resultado do `await`, quebrando o decorator já na primeira chamada
87
+ com `TypeError: cannot pickle 'coroutine' object`. O decorator agora detecta
88
+ `inspect.iscoroutinefunction` e gera um wrapper `async` que faz o `await`
89
+ antes do `set`. (BACKLOG2 P0-2)
90
+
91
+ ## [0.2.0]
92
+
93
+ ### Adicionado
94
+
95
+ - Expiração em background via `Sweeper` (thread dedicada com `Weak<T>`,
96
+ encerramento limpo no `Drop`).
97
+ - Evicção por `max_size` com política LRU.
98
+ - Exemplos para FastAPI e Django.
99
+
100
+ ## [0.1.x]
101
+
102
+ ### Adicionado
103
+
104
+ - `Cache` com `set` / `get(default=...)` / `delete` / `exists` / `keys` /
105
+ `len` / `clear` / `cleanup_expired` / `stats`.
106
+ - TTL por entrada com expiração preguiçosa.
107
+ - `stats()` cumulativo baseado em `AtomicU64`.
108
+ - Decorator de memoização `@cache.cached(ttl=..., key=...)`.
109
+ - Core em Rust sobre `DashMap`, valores serializados com `pickle`.
@@ -202,7 +202,7 @@ dependencies = [
202
202
 
203
203
  [[package]]
204
204
  name = "rust_py_cache"
205
- version = "0.2.1"
205
+ version = "0.2.3"
206
206
  dependencies = [
207
207
  "dashmap",
208
208
  "pyo3",
@@ -1,21 +1,30 @@
1
1
  [package]
2
2
  name = "rust_py_cache"
3
- version = "0.2.1"
3
+ version = "0.2.3"
4
4
  edition = "2021"
5
+ license = "MIT"
5
6
  readme = "README.md"
6
7
 
7
8
  # Biblioteca de extensão para Python:
8
- # - crate-type = ["cdylib"] -> gera um .so/.dylib carregável pelo Python
9
+ # - cdylib -> gera o .so/.dylib carregável pelo Python
10
+ # - rlib -> alvo de biblioteca normal, que o `cargo test` usa para montar
11
+ # o harness de teste
9
12
  # - name = "_rust_py_cache" -> nome do módulo nativo (importado pelo __init__.py)
10
13
  [lib]
11
14
  name = "_rust_py_cache"
12
- crate-type = ["cdylib"]
15
+ crate-type = ["cdylib", "rlib"]
13
16
 
14
17
  [dependencies]
15
18
  # abi3-py310: compila contra a Stable ABI do CPython 3.10+, gerando uma única
16
19
  # wheel compatível com 3.10, 3.11, 3.12, 3.13...
17
- # extension-module: não linka o binário do Python (o interpretador resolve em runtime).
18
- pyo3 = { version = "0.24", features = ["abi3-py310", "extension-module"] }
20
+ #
21
+ # extension-module NÃO fica aqui: ele manda o linker deixar os símbolos da
22
+ # libpython indefinidos, o que é correto para a wheel mas frágil para o binário
23
+ # de teste no Linux (o macOS tolera com -undefined dynamic_lookup). Vive em
24
+ # [features] no fim do arquivo, ligado por padrão, para que
25
+ # `cargo test --no-default-features` possa desligá-lo. É o mesmo arranjo dos
26
+ # outros quatro projetos.
27
+ pyo3 = { version = "0.24", features = ["abi3-py310"] }
19
28
 
20
29
  # DashMap: HashMap concorrente (lock por shard). Permite get/set de várias threads
21
30
  # Python sem um Mutex global no caminho crítico. Usado pelo RustCache (Etapa 4).
@@ -30,3 +39,10 @@ thiserror = "2"
30
39
  # serde_json = "1" # serializer JSON
31
40
  # No MVP o TTL usa std::time::SystemTime (epoch em ms), então o crate `time`
32
41
  # também não é necessário ainda.
42
+
43
+ [features]
44
+ # maturin constrói com as features default (a wheel ganha extension-module);
45
+ # a CI roda `cargo test --no-default-features --lib` para obter um binário de
46
+ # teste que consegue linkar contra a libpython.
47
+ extension-module = ["pyo3/extension-module"]
48
+ default = ["extension-module"]
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Roberto Lima
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,68 @@
1
+ # Makefile compartilhado dos `rust_py_*`.
2
+ #
3
+ # Existe para que a CI e a pessoa rodem exatamente o mesmo comando. Quando o
4
+ # lint da CI diverge do lint local, quem perde é sempre quem abriu o PR.
5
+ #
6
+ # A toolchain NÃO é fixada por `rust-toolchain.toml` de propósito: esse arquivo
7
+ # vale para toda invocação do cargo, inclusive a dos containers manylinux do
8
+ # release, e um download de toolchain no meio de uma publicação no PyPI é risco
9
+ # sem contrapartida. A fixação vive no job de lint da CI, que é onde
10
+ # `-D warnings` realmente morde. Localmente, use uma stable recente.
11
+
12
+ PYTHON ?= python3
13
+
14
+ # Fixa qual Python o pyo3 usa na compilação. Sem isto ele pega o primeiro do
15
+ # PATH, que pode não ser o mesmo do `make dev` — e aí o binário de teste linka
16
+ # contra uma libpython e o pytest roda contra outra.
17
+ export PYO3_PYTHON ?= $(PYTHON)
18
+
19
+ # No macOS o binário de `cargo test` carrega a libpython em runtime, e o dyld
20
+ # não a encontra sozinho: o erro é `Library not loaded: @rpath/libpython3.X.dylib`,
21
+ # em SIGABRT, depois de compilar com sucesso — parece falha de teste e não é.
22
+ # Os caminhos padrão entram junto porque definir DYLD_FALLBACK_LIBRARY_PATH
23
+ # *substitui* a lista default do dyld em vez de acrescentar a ela (senão o
24
+ # erro seguinte é libiconv.2.dylib).
25
+ PY_LIBDIR := $(shell $(PYTHON) -c 'import sysconfig; print(sysconfig.get_config_var("LIBDIR") or "")' 2>/dev/null)
26
+ export DYLD_FALLBACK_LIBRARY_PATH := $(PY_LIBDIR):/opt/homebrew/lib:/usr/local/lib:/usr/lib
27
+
28
+ .PHONY: help dev test lint fmt clippy test-rust test-python clean
29
+
30
+ help:
31
+ @echo "make dev instala o pacote em modo editável com os extras de dev"
32
+ @echo "make test cargo test + pytest"
33
+ @echo "make lint cargo fmt --check + cargo clippy -D warnings"
34
+ @echo "make fmt aplica a formatação (escreve nos arquivos)"
35
+
36
+ # `pip install` aciona o backend maturin, compila a extensão e instala.
37
+ # Ao contrário de `maturin develop`, não exige um virtualenv ativo — que é
38
+ # exatamente a armadilha que custou um release do monitor (ver P1-9).
39
+ dev:
40
+ $(PYTHON) -m pip install --upgrade pip
41
+ $(PYTHON) -m pip install -e ".[dev]"
42
+
43
+ test: test-rust test-python
44
+
45
+ # --no-default-features desliga `extension-module`, que impede o binário de
46
+ # teste de linkar contra a libpython no Linux. --lib porque os testes de
47
+ # integração precisam do módulo nativo, e esses ficam do lado Python.
48
+ test-rust:
49
+ cargo test --no-default-features --lib
50
+
51
+ test-python:
52
+ pytest -q
53
+
54
+ lint: fmt-check clippy
55
+
56
+ .PHONY: fmt-check
57
+ fmt-check:
58
+ cargo fmt --all -- --check
59
+
60
+ clippy:
61
+ cargo clippy --all-targets --all-features -- -D warnings
62
+
63
+ fmt:
64
+ cargo fmt --all
65
+
66
+ clean:
67
+ cargo clean
68
+ rm -rf dist build *.egg-info .pytest_cache
@@ -1,17 +1,20 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rust-py-cache
3
- Version: 0.2.1
3
+ Version: 0.2.3
4
4
  Classifier: Programming Language :: Rust
5
5
  Classifier: Programming Language :: Python :: 3
6
6
  Classifier: Programming Language :: Python :: Implementation :: CPython
7
- Classifier: License :: OSI Approved :: MIT License
8
7
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
9
8
  Classifier: Topic :: Database
10
9
  Classifier: Topic :: System :: Distributed Computing
10
+ Requires-Dist: pytest>=7.0 ; extra == 'dev'
11
+ Requires-Dist: maturin>=1.7 ; extra == 'dev'
12
+ Provides-Extra: dev
13
+ License-File: LICENSE
11
14
  Summary: An ultra-fast local cache for Python, powered by Rust.
12
15
  Keywords: cache,rust,pyo3,ttl,performance
13
16
  Author-email: Roberto Lima <robertolima.izphera@gmail.com>
14
- License: MIT
17
+ License-Expression: MIT
15
18
  Requires-Python: >=3.10
16
19
  Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
17
20
  Project-URL: Homepage, https://github.com/robertolima/rust-py-cache
@@ -0,0 +1,11 @@
1
+ # Configuração compartilhada do clippy para os cinco `rust_py_*`.
2
+ #
3
+ # O padrão do clippy para `too_many_arguments` é 7. Estes projetos expõem
4
+ # construtores PyO3 que espelham a assinatura Python — `add_job()` do
5
+ # scheduler leva 8 parâmetros porque a API pública leva 8 (retry_backoff,
6
+ # timeout, max_instances e afins). Reagrupar em struct para agradar o lint
7
+ # pioraria a API do lado Python, que é a que o usuário vê.
8
+ #
9
+ # O teto sobe para 10 em vez de o lint ser desligado: acima de 10 o aviso
10
+ # volta a ser informação útil.
11
+ too-many-arguments-threshold = 10
@@ -1,26 +1,37 @@
1
1
  [build-system]
2
- requires = ["maturin>=1.7,<2.0"]
2
+ # Piso 1.14 de propósito: versões anteriores da maturin não emitem o
3
+ # `License-Expression` do PEP 639, e o resultado é uma release publicada
4
+ # no PyPI sem metadado de licença nenhum — só o classifier. Foi o que
5
+ # aconteceu com audit 0.3.2, monitor 0.4.1 e rate_limit 0.2.1 (ver P1-4).
6
+ requires = ["maturin>=1.14,<2.0"]
3
7
  build-backend = "maturin"
4
8
 
5
9
  [project]
6
10
  name = "rust-py-cache"
7
- version = "0.2.1"
11
+ version = "0.2.3"
8
12
  description = "An ultra-fast local cache for Python, powered by Rust."
9
13
  readme = "README.md"
10
14
  requires-python = ">=3.10"
11
- license = { text = "MIT" }
15
+ # Forma do PEP 639: expressão SPDX + arquivos de licença declarados.
16
+ # O classifier "License :: OSI Approved" saiu de propósito — é deprecado
17
+ # quando existe License-Expression, e os dois juntos são mutuamente
18
+ # exclusivos no Metadata 2.4.
19
+ license = "MIT"
20
+ license-files = ["LICENSE"]
12
21
  authors = [{ name = "Roberto Lima", email = "robertolima.izphera@gmail.com" }]
13
22
  keywords = ["cache", "rust", "pyo3", "ttl", "performance"]
14
23
  classifiers = [
15
24
  "Programming Language :: Rust",
16
25
  "Programming Language :: Python :: 3",
17
26
  "Programming Language :: Python :: Implementation :: CPython",
18
- "License :: OSI Approved :: MIT License",
19
27
  "Topic :: Software Development :: Libraries :: Python Modules",
20
28
  "Topic :: Database",
21
29
  "Topic :: System :: Distributed Computing",
22
30
  ]
23
31
 
32
+ [project.optional-dependencies]
33
+ dev = ["pytest>=7.0", "maturin>=1.7"]
34
+
24
35
  [project.urls]
25
36
  Homepage = "https://github.com/robertolima/rust-py-cache"
26
37
 
@@ -29,5 +40,6 @@ Homepage = "https://github.com/robertolima/rust-py-cache"
29
40
  python-source = "python"
30
41
  # Compila o .so como submódulo nativo dentro do pacote Python.
31
42
  module-name = "rust_py_cache._rust_py_cache"
32
- # Gera wheel abi3 (uma wheel para 3.10+).
33
- features = ["pyo3/extension-module"]
43
+ # extension-module vem das features default do crate (ver Cargo.toml), então
44
+ # não precisa ser repetido aqui — e repetir reativaria a feature mesmo num
45
+ # build que pediu --no-default-features de propósito.
@@ -19,4 +19,4 @@ class Cache(_RustCache):
19
19
 
20
20
 
21
21
  __all__ = ["Cache", "hello"]
22
- __version__ = "0.2.1"
22
+ __version__ = "0.2.3"
@@ -187,7 +187,11 @@ impl RustCache {
187
187
 
188
188
  let mut removed = 0u64;
189
189
  for key in expired {
190
- if self.store.remove_if(&key, |_, v| v.is_expired(now)).is_some() {
190
+ if self
191
+ .store
192
+ .remove_if(&key, |_, v| v.is_expired(now))
193
+ .is_some()
194
+ {
191
195
  removed += 1;
192
196
  }
193
197
  }
@@ -155,12 +155,7 @@ impl Cache {
155
155
  /// - Chave expirada → remove a entrada (expiração lazy) e devolve `default`.
156
156
  /// - Caso contrário → o objeto Python original, desserializado por `pickle`.
157
157
  #[pyo3(signature = (key, default=None))]
158
- fn get(
159
- &self,
160
- py: Python<'_>,
161
- key: &str,
162
- default: Option<Py<PyAny>>,
163
- ) -> PyResult<Py<PyAny>> {
158
+ fn get(&self, py: Python<'_>, key: &str, default: Option<Py<PyAny>>) -> PyResult<Py<PyAny>> {
164
159
  match self.inner.get(key, now_ms()) {
165
160
  Some(bytes) => loads(py, &bytes),
166
161
  None => Ok(default.unwrap_or_else(|| py.None())),
@@ -1,77 +0,0 @@
1
- name: CI
2
-
3
- on:
4
- push:
5
- branches: [main]
6
- pull_request:
7
-
8
- jobs:
9
- test:
10
- name: cargo test + pytest
11
- runs-on: ${{ matrix.os }}
12
- strategy:
13
- fail-fast: false
14
- matrix:
15
- os: [ubuntu-latest, macos-latest]
16
- python-version: ["3.10", "3.12", "3.13"]
17
- steps:
18
- - uses: actions/checkout@v4
19
-
20
- - name: Set up Python ${{ matrix.python-version }}
21
- uses: actions/setup-python@v5
22
- with:
23
- python-version: ${{ matrix.python-version }}
24
-
25
- - name: Set up Rust
26
- uses: dtolnay/rust-toolchain@stable
27
-
28
- - name: Cache cargo
29
- uses: Swatinem/rust-cache@v2
30
-
31
- # Testes do core Rust (puro, sem Python).
32
- - name: cargo test
33
- run: cargo test --verbose
34
-
35
- # maturin develop exige um virtualenv ativo. O setup-python não cria um,
36
- # então criamos o .venv e o exportamos para os próximos steps via
37
- # GITHUB_ENV/GITHUB_PATH (cada step roda num shell novo).
38
- - name: Create virtualenv
39
- run: |
40
- python -m venv .venv
41
- echo "VIRTUAL_ENV=$PWD/.venv" >> "$GITHUB_ENV"
42
- echo "$PWD/.venv/bin" >> "$GITHUB_PATH"
43
-
44
- - name: Install Python deps
45
- run: |
46
- python -m pip install --upgrade pip
47
- pip install maturin pytest
48
-
49
- # Compila a extensão e instala no .venv.
50
- - name: maturin develop
51
- run: maturin develop
52
-
53
- - name: pytest
54
- run: pytest -q
55
-
56
- build-wheels:
57
- name: build wheels (abi3)
58
- runs-on: ${{ matrix.os }}
59
- needs: test
60
- strategy:
61
- matrix:
62
- os: [ubuntu-latest, macos-latest, windows-latest]
63
- steps:
64
- - uses: actions/checkout@v4
65
- - uses: actions/setup-python@v5
66
- with:
67
- python-version: "3.12"
68
- - name: Build wheels
69
- uses: PyO3/maturin-action@v1
70
- with:
71
- command: build
72
- args: --release --out dist
73
- - name: Upload artifacts
74
- uses: actions/upload-artifact@v4
75
- with:
76
- name: wheels-${{ matrix.os }}
77
- path: dist
@@ -1,57 +0,0 @@
1
- name: Release
2
-
3
- # Dispara ao publicar uma tag de versão (ex.: v0.1.0). Constrói as wheels para
4
- # todas as plataformas + sdist e publica no PyPI via Trusted Publishing (OIDC),
5
- # sem precisar guardar um token no repositório.
6
- on:
7
- push:
8
- tags: ["v*"]
9
-
10
- permissions:
11
- contents: read
12
-
13
- jobs:
14
- build:
15
- name: build (${{ matrix.os }})
16
- runs-on: ${{ matrix.os }}
17
- strategy:
18
- matrix:
19
- os: [ubuntu-latest, macos-latest, windows-latest]
20
- steps:
21
- - uses: actions/checkout@v4
22
- - uses: actions/setup-python@v5
23
- with:
24
- python-version: "3.12"
25
- - name: Build wheels
26
- uses: PyO3/maturin-action@v1
27
- with:
28
- command: build
29
- args: --release --out dist
30
- - name: Build sdist
31
- if: matrix.os == 'ubuntu-latest'
32
- uses: PyO3/maturin-action@v1
33
- with:
34
- command: sdist
35
- args: --out dist
36
- - uses: actions/upload-artifact@v4
37
- with:
38
- name: dist-${{ matrix.os }}
39
- path: dist
40
-
41
- publish:
42
- name: publish to PyPI
43
- needs: build
44
- runs-on: ubuntu-latest
45
- environment: pypi
46
- permissions:
47
- id-token: write # necessário para o Trusted Publishing (OIDC)
48
- steps:
49
- - uses: actions/download-artifact@v4
50
- with:
51
- pattern: dist-*
52
- merge-multiple: true
53
- path: dist
54
- - name: Publish
55
- uses: pypa/gh-action-pypi-publish@release/v1
56
- with:
57
- packages-dir: dist
@@ -1,36 +0,0 @@
1
- # Changelog
2
-
3
- Todas as mudanças relevantes deste projeto são registradas aqui.
4
-
5
- O formato segue [Keep a Changelog](https://keepachangelog.com/pt-BR/1.1.0/),
6
- e o versionamento segue [SemVer](https://semver.org/lang/pt-BR/).
7
-
8
- ## [0.2.1] - 2026-08-11
9
-
10
- ### Corrigido
11
-
12
- - `@cache.cached` sobre função `async def` guardava no cache o objeto coroutine
13
- em vez do resultado do `await`, quebrando o decorator já na primeira chamada
14
- com `TypeError: cannot pickle 'coroutine' object`. O decorator agora detecta
15
- `inspect.iscoroutinefunction` e gera um wrapper `async` que faz o `await`
16
- antes do `set`. (BACKLOG2 P0-2)
17
-
18
- ## [0.2.0]
19
-
20
- ### Adicionado
21
-
22
- - Expiração em background via `Sweeper` (thread dedicada com `Weak<T>`,
23
- encerramento limpo no `Drop`).
24
- - Evicção por `max_size` com política LRU.
25
- - Exemplos para FastAPI e Django.
26
-
27
- ## [0.1.x]
28
-
29
- ### Adicionado
30
-
31
- - `Cache` com `set` / `get(default=...)` / `delete` / `exists` / `keys` /
32
- `len` / `clear` / `cleanup_expired` / `stats`.
33
- - TTL por entrada com expiração preguiçosa.
34
- - `stats()` cumulativo baseado em `AtomicU64`.
35
- - Decorator de memoização `@cache.cached(ttl=..., key=...)`.
36
- - Core em Rust sobre `DashMap`, valores serializados com `pickle`.
File without changes
File without changes
File without changes
File without changes