pybasedb-json 0.2.0__tar.gz → 0.4.0__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.
- pybasedb_json-0.4.0/PKG-INFO +146 -0
- pybasedb_json-0.4.0/README.md +120 -0
- pybasedb_json-0.4.0/pybase/__init__.py +44 -0
- pybasedb_json-0.4.0/pybase/backup.py +100 -0
- pybasedb_json-0.4.0/pybase/collections.py +309 -0
- pybasedb_json-0.4.0/pybase/crypto.py +59 -0
- pybasedb_json-0.4.0/pybase/database.py +151 -0
- pybasedb_json-0.4.0/pybase/exceptions.py +30 -0
- pybasedb_json-0.4.0/pybase/hooks.py +133 -0
- pybasedb_json-0.4.0/pybase/index.py +54 -0
- pybasedb_json-0.4.0/pybase/media.py +214 -0
- pybasedb_json-0.4.0/pybase/pybase.py +57 -0
- pybasedb_json-0.4.0/pybase/query.py +28 -0
- pybasedb_json-0.4.0/pybase/server.py +222 -0
- pybasedb_json-0.4.0/pybase/utils.py +23 -0
- pybasedb_json-0.4.0/pybase/validation.py +382 -0
- pybasedb_json-0.4.0/pybasedb_json.egg-info/PKG-INFO +146 -0
- {pybasedb_json-0.2.0 → pybasedb_json-0.4.0}/pybasedb_json.egg-info/SOURCES.txt +8 -0
- {pybasedb_json-0.2.0 → pybasedb_json-0.4.0}/pyproject.toml +6 -3
- pybasedb_json-0.2.0/PKG-INFO +0 -92
- pybasedb_json-0.2.0/README.md +0 -69
- pybasedb_json-0.2.0/pybase/__init__.py +0 -13
- pybasedb_json-0.2.0/pybase/collections.py +0 -133
- pybasedb_json-0.2.0/pybase/database.py +0 -72
- pybasedb_json-0.2.0/pybase/exceptions.py +0 -14
- pybasedb_json-0.2.0/pybase/query.py +0 -73
- pybasedb_json-0.2.0/pybase/utils.py +0 -11
- pybasedb_json-0.2.0/pybasedb_json.egg-info/PKG-INFO +0 -92
- {pybasedb_json-0.2.0 → pybasedb_json-0.4.0}/pybasedb_json.egg-info/dependency_links.txt +0 -0
- {pybasedb_json-0.2.0 → pybasedb_json-0.4.0}/pybasedb_json.egg-info/top_level.txt +0 -0
- {pybasedb_json-0.2.0 → pybasedb_json-0.4.0}/setup.cfg +0 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pybasedb-json
|
|
3
|
+
Version: 0.4.0
|
|
4
|
+
Summary: Banco de dados local com JSON — schemas, decorator API, server REST, criptografia, zero dependencias
|
|
5
|
+
Author-email: Marcos Gomes <marcosgabrielgomes110@gmail.com>
|
|
6
|
+
Maintainer-email: Marcos Gomes <marcosgabrielgomes110@gmail.com>
|
|
7
|
+
License: MIT
|
|
8
|
+
Project-URL: Homepage, https://github.com/marcosgabrielgomes110-collab/Pybase
|
|
9
|
+
Project-URL: Documentation, https://github.com/marcosgabrielgomes110-collab/Pybase/tree/main/docs
|
|
10
|
+
Project-URL: Repository, https://github.com/marcosgabrielgomes110-collab/Pybase
|
|
11
|
+
Keywords: database,json,local,embedded,nosql,thread-safe,rest-api,schema,media
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
21
|
+
Classifier: Topic :: Database
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
|
|
24
|
+
Requires-Python: >=3.10
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
|
|
27
|
+
<p align="center">
|
|
28
|
+
<img src="assets/logo/logo.png" width="120" alt="Pybase logo">
|
|
29
|
+
</p>
|
|
30
|
+
|
|
31
|
+
# Pybase v0.4
|
|
32
|
+
|
|
33
|
+
Base para criacao de bancos de dados locais com JSON. Zero dependencias, 100% stdlib.
|
|
34
|
+
Operacoes de escrita atomicas, thread-safe.
|
|
35
|
+
|
|
36
|
+
Filosofia **Lego**: cada modulo e independente — voce monta o que precisa.
|
|
37
|
+
|
|
38
|
+
## Instalacao
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pip install pybasedb-json
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Para o servidor REST (opcional):
|
|
45
|
+
```bash
|
|
46
|
+
pip install Flask
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Uso rapido
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
from pybase import pybase as pb
|
|
53
|
+
|
|
54
|
+
# Banco de dados (sem senha ou com senha)
|
|
55
|
+
db = pb.Database("./data/mydb")
|
|
56
|
+
# db = pb.Database("./data/mydb", password="senha").open("senha")
|
|
57
|
+
|
|
58
|
+
# Schema com decorator (v0.4+)
|
|
59
|
+
@db.schema.users
|
|
60
|
+
class User:
|
|
61
|
+
name: str
|
|
62
|
+
email: str
|
|
63
|
+
age: int = 0
|
|
64
|
+
active: bool = True
|
|
65
|
+
|
|
66
|
+
# Ou com T() explicito
|
|
67
|
+
# from pybase.validation import T
|
|
68
|
+
# db.schema.users = pb.Schema({"name": pb.T(str), "age": pb.T(int, default=0)})
|
|
69
|
+
|
|
70
|
+
# Colecao com validacao automatica
|
|
71
|
+
users = db["users"]
|
|
72
|
+
users.add({"name": "marcos", "email": "m@m.com"}) # auto-ID
|
|
73
|
+
|
|
74
|
+
# SDK client
|
|
75
|
+
doc = users["1"].get()
|
|
76
|
+
users.find(age=0)
|
|
77
|
+
|
|
78
|
+
# Consultas direto na colecao (v0.4+)
|
|
79
|
+
users.sort("name").limit(10).find(active=True)
|
|
80
|
+
users.find_one(name="ana")
|
|
81
|
+
users.exists(name="joao")
|
|
82
|
+
|
|
83
|
+
# Servidor REST (requer Flask)
|
|
84
|
+
pb.Server(db).run(port=4560) # auto-detecta colecoes
|
|
85
|
+
# Swagger UI: http://localhost:4560/mydb/docs/
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Modulos
|
|
89
|
+
|
|
90
|
+
| Modulo | Arquivo | Descricao | Deps |
|
|
91
|
+
|--------|---------|-----------|------|
|
|
92
|
+
| Database | `pybase/database.py` | Gerencia diretorio de tabelas JSON | 0 |
|
|
93
|
+
| Collection | `pybase/collections.py` | CRUD atomico thread-safe + query integrado | 0 |
|
|
94
|
+
| Query | `pybase/query.py` | Thin wrapper sobre Collection | 0 |
|
|
95
|
+
| Schema | `pybase/validation.py` | Validacao com T(), decorator, from_example | 0 |
|
|
96
|
+
| Server | `pybase/server.py` | REST API automatica + Swagger UI | Flask |
|
|
97
|
+
| Media | `pybase/media.py` | Upload/download de imagens | 0 |
|
|
98
|
+
| Backup | `pybase/backup.py` | Snapshot e restore (seletivo) | 0 |
|
|
99
|
+
| Crypto | `pybase/crypto.py` | Criptografia stdlib (scrypt + XOR) | 0 |
|
|
100
|
+
|
|
101
|
+
## Novidades da v0.4
|
|
102
|
+
|
|
103
|
+
- **API simplificada** — `Database("./data/mydb")` sem senha obrigatoria
|
|
104
|
+
- **`update`/`delete`** — nomes em ingles, sem aliases pt-br
|
|
105
|
+
- **Auto-ID** — `users.add({"name": "x"})` gera UUID automatico
|
|
106
|
+
- **`db["colecao"]`** — acesso direto via `__getitem__`
|
|
107
|
+
- **Query integrado** — `collection.sort().limit().find()` direto
|
|
108
|
+
- **`@db.schema.nome`** — schema via decorator de classe
|
|
109
|
+
- **`T()`** — nova API de tipos com kwargs (`T(int, default=0)`)
|
|
110
|
+
- **`Schema.from_example()`** — schema inferido de dados reais
|
|
111
|
+
- **`import global`** — `from pybase import pybase`
|
|
112
|
+
- **PBKDF2** — hashing de senha com salt (600k iteracoes)
|
|
113
|
+
- **Criptografia** — `encrypt()`/`decrypt()` stdlib puro
|
|
114
|
+
- **Server auto-detect** — `Server(db).run()` descobre colecoes
|
|
115
|
+
- **Erros descritivos** — com path + valor + sugestao
|
|
116
|
+
|
|
117
|
+
## Estrutura
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
Pybase/
|
|
121
|
+
pybase/
|
|
122
|
+
__init__.py # Database, Collection, Query, Server, Schema, T
|
|
123
|
+
pybase.py # from pybase import pybase
|
|
124
|
+
database.py # Database, SchemaRegistrar
|
|
125
|
+
collections.py # Collection, DocRef, QueryBuilder, SchemaCollection
|
|
126
|
+
query.py # Query wrapper
|
|
127
|
+
exceptions.py # PybaseError > DatabaseError, CollectionError, ...
|
|
128
|
+
validation.py # Schema, T, ValidationError
|
|
129
|
+
server.py # Server REST (Flask)
|
|
130
|
+
media.py # MediaManager
|
|
131
|
+
index.py # Index (hash O(1))
|
|
132
|
+
hooks.py # HooksCollection
|
|
133
|
+
backup.py # take, restore
|
|
134
|
+
crypto.py # encrypt, decrypt (stdlib)
|
|
135
|
+
utils.py # PBKDF2 encode/verify
|
|
136
|
+
docs/ # Documentacao completa
|
|
137
|
+
pyproject.toml
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Documentacao
|
|
141
|
+
|
|
142
|
+
Veja [docs/](docs/index.md) com referencia completa de cada classe e metodo.
|
|
143
|
+
|
|
144
|
+
## Licenca
|
|
145
|
+
|
|
146
|
+
MIT
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="assets/logo/logo.png" width="120" alt="Pybase logo">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
# Pybase v0.4
|
|
6
|
+
|
|
7
|
+
Base para criacao de bancos de dados locais com JSON. Zero dependencias, 100% stdlib.
|
|
8
|
+
Operacoes de escrita atomicas, thread-safe.
|
|
9
|
+
|
|
10
|
+
Filosofia **Lego**: cada modulo e independente — voce monta o que precisa.
|
|
11
|
+
|
|
12
|
+
## Instalacao
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pip install pybasedb-json
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Para o servidor REST (opcional):
|
|
19
|
+
```bash
|
|
20
|
+
pip install Flask
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Uso rapido
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
from pybase import pybase as pb
|
|
27
|
+
|
|
28
|
+
# Banco de dados (sem senha ou com senha)
|
|
29
|
+
db = pb.Database("./data/mydb")
|
|
30
|
+
# db = pb.Database("./data/mydb", password="senha").open("senha")
|
|
31
|
+
|
|
32
|
+
# Schema com decorator (v0.4+)
|
|
33
|
+
@db.schema.users
|
|
34
|
+
class User:
|
|
35
|
+
name: str
|
|
36
|
+
email: str
|
|
37
|
+
age: int = 0
|
|
38
|
+
active: bool = True
|
|
39
|
+
|
|
40
|
+
# Ou com T() explicito
|
|
41
|
+
# from pybase.validation import T
|
|
42
|
+
# db.schema.users = pb.Schema({"name": pb.T(str), "age": pb.T(int, default=0)})
|
|
43
|
+
|
|
44
|
+
# Colecao com validacao automatica
|
|
45
|
+
users = db["users"]
|
|
46
|
+
users.add({"name": "marcos", "email": "m@m.com"}) # auto-ID
|
|
47
|
+
|
|
48
|
+
# SDK client
|
|
49
|
+
doc = users["1"].get()
|
|
50
|
+
users.find(age=0)
|
|
51
|
+
|
|
52
|
+
# Consultas direto na colecao (v0.4+)
|
|
53
|
+
users.sort("name").limit(10).find(active=True)
|
|
54
|
+
users.find_one(name="ana")
|
|
55
|
+
users.exists(name="joao")
|
|
56
|
+
|
|
57
|
+
# Servidor REST (requer Flask)
|
|
58
|
+
pb.Server(db).run(port=4560) # auto-detecta colecoes
|
|
59
|
+
# Swagger UI: http://localhost:4560/mydb/docs/
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Modulos
|
|
63
|
+
|
|
64
|
+
| Modulo | Arquivo | Descricao | Deps |
|
|
65
|
+
|--------|---------|-----------|------|
|
|
66
|
+
| Database | `pybase/database.py` | Gerencia diretorio de tabelas JSON | 0 |
|
|
67
|
+
| Collection | `pybase/collections.py` | CRUD atomico thread-safe + query integrado | 0 |
|
|
68
|
+
| Query | `pybase/query.py` | Thin wrapper sobre Collection | 0 |
|
|
69
|
+
| Schema | `pybase/validation.py` | Validacao com T(), decorator, from_example | 0 |
|
|
70
|
+
| Server | `pybase/server.py` | REST API automatica + Swagger UI | Flask |
|
|
71
|
+
| Media | `pybase/media.py` | Upload/download de imagens | 0 |
|
|
72
|
+
| Backup | `pybase/backup.py` | Snapshot e restore (seletivo) | 0 |
|
|
73
|
+
| Crypto | `pybase/crypto.py` | Criptografia stdlib (scrypt + XOR) | 0 |
|
|
74
|
+
|
|
75
|
+
## Novidades da v0.4
|
|
76
|
+
|
|
77
|
+
- **API simplificada** — `Database("./data/mydb")` sem senha obrigatoria
|
|
78
|
+
- **`update`/`delete`** — nomes em ingles, sem aliases pt-br
|
|
79
|
+
- **Auto-ID** — `users.add({"name": "x"})` gera UUID automatico
|
|
80
|
+
- **`db["colecao"]`** — acesso direto via `__getitem__`
|
|
81
|
+
- **Query integrado** — `collection.sort().limit().find()` direto
|
|
82
|
+
- **`@db.schema.nome`** — schema via decorator de classe
|
|
83
|
+
- **`T()`** — nova API de tipos com kwargs (`T(int, default=0)`)
|
|
84
|
+
- **`Schema.from_example()`** — schema inferido de dados reais
|
|
85
|
+
- **`import global`** — `from pybase import pybase`
|
|
86
|
+
- **PBKDF2** — hashing de senha com salt (600k iteracoes)
|
|
87
|
+
- **Criptografia** — `encrypt()`/`decrypt()` stdlib puro
|
|
88
|
+
- **Server auto-detect** — `Server(db).run()` descobre colecoes
|
|
89
|
+
- **Erros descritivos** — com path + valor + sugestao
|
|
90
|
+
|
|
91
|
+
## Estrutura
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
Pybase/
|
|
95
|
+
pybase/
|
|
96
|
+
__init__.py # Database, Collection, Query, Server, Schema, T
|
|
97
|
+
pybase.py # from pybase import pybase
|
|
98
|
+
database.py # Database, SchemaRegistrar
|
|
99
|
+
collections.py # Collection, DocRef, QueryBuilder, SchemaCollection
|
|
100
|
+
query.py # Query wrapper
|
|
101
|
+
exceptions.py # PybaseError > DatabaseError, CollectionError, ...
|
|
102
|
+
validation.py # Schema, T, ValidationError
|
|
103
|
+
server.py # Server REST (Flask)
|
|
104
|
+
media.py # MediaManager
|
|
105
|
+
index.py # Index (hash O(1))
|
|
106
|
+
hooks.py # HooksCollection
|
|
107
|
+
backup.py # take, restore
|
|
108
|
+
crypto.py # encrypt, decrypt (stdlib)
|
|
109
|
+
utils.py # PBKDF2 encode/verify
|
|
110
|
+
docs/ # Documentacao completa
|
|
111
|
+
pyproject.toml
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Documentacao
|
|
115
|
+
|
|
116
|
+
Veja [docs/](docs/index.md) com referencia completa de cada classe e metodo.
|
|
117
|
+
|
|
118
|
+
## Licenca
|
|
119
|
+
|
|
120
|
+
MIT
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
try:
|
|
2
|
+
from .server import Server
|
|
3
|
+
except ImportError:
|
|
4
|
+
Server = None
|
|
5
|
+
|
|
6
|
+
from .collections import Collection, DocRef, QueryBuilder, SchemaCollection
|
|
7
|
+
from .database import Database
|
|
8
|
+
from .exceptions import (
|
|
9
|
+
CollectionError,
|
|
10
|
+
DatabaseError,
|
|
11
|
+
DocumentNotFoundError,
|
|
12
|
+
EncryptionError,
|
|
13
|
+
MediaError,
|
|
14
|
+
PybaseError,
|
|
15
|
+
QueryError,
|
|
16
|
+
ValidationError,
|
|
17
|
+
)
|
|
18
|
+
from .query import Query
|
|
19
|
+
|
|
20
|
+
try:
|
|
21
|
+
from .validation import Schema, T
|
|
22
|
+
except ImportError:
|
|
23
|
+
Schema = None
|
|
24
|
+
T = None
|
|
25
|
+
|
|
26
|
+
__all__ = [
|
|
27
|
+
"Collection",
|
|
28
|
+
"Database",
|
|
29
|
+
"DocRef",
|
|
30
|
+
"DocumentNotFoundError",
|
|
31
|
+
"EncryptionError",
|
|
32
|
+
"MediaError",
|
|
33
|
+
"PybaseError",
|
|
34
|
+
"DatabaseError",
|
|
35
|
+
"CollectionError",
|
|
36
|
+
"Query",
|
|
37
|
+
"QueryBuilder",
|
|
38
|
+
"QueryError",
|
|
39
|
+
"Schema",
|
|
40
|
+
"SchemaCollection",
|
|
41
|
+
"Server",
|
|
42
|
+
"T",
|
|
43
|
+
"ValidationError",
|
|
44
|
+
]
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# Lego piece: optional backup / restore for Pybase databases.
|
|
2
|
+
# from pybase.backup import take, restore, list_snapshots
|
|
3
|
+
# snap = take(db) # timestamped .json.gz
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import gzip
|
|
8
|
+
import json
|
|
9
|
+
import os
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import TYPE_CHECKING
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from .database import Database
|
|
16
|
+
|
|
17
|
+
_BACKUP_DIR = "backups"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def take(
|
|
21
|
+
db: Database,
|
|
22
|
+
dest: str | Path | None = None,
|
|
23
|
+
*,
|
|
24
|
+
compress: bool = True,
|
|
25
|
+
collections: list[str] | None = None,
|
|
26
|
+
) -> Path:
|
|
27
|
+
backup_dir = Path(dest) if dest else (db.path_dir / _BACKUP_DIR)
|
|
28
|
+
backup_dir.mkdir(parents=True, exist_ok=True)
|
|
29
|
+
|
|
30
|
+
ts = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
31
|
+
ext = ".json.gz" if compress else ".json"
|
|
32
|
+
out_path = backup_dir / f"{db.name}_{ts}{ext}"
|
|
33
|
+
|
|
34
|
+
names = collections if collections else db.collections
|
|
35
|
+
snapshot: dict[str, list[dict]] = {}
|
|
36
|
+
for name in names:
|
|
37
|
+
snapshot[name] = db.collection(name).all()
|
|
38
|
+
|
|
39
|
+
raw = json.dumps(snapshot, indent=2).encode("utf-8")
|
|
40
|
+
|
|
41
|
+
if compress:
|
|
42
|
+
# mtime=0 makes the gzip deterministic (same content → same bytes)
|
|
43
|
+
with gzip.GzipFile(out_path, "wb", mtime=0) as f:
|
|
44
|
+
f.write(raw)
|
|
45
|
+
else:
|
|
46
|
+
out_path.write_bytes(raw)
|
|
47
|
+
|
|
48
|
+
return out_path
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def restore(
|
|
52
|
+
db_dir: str | Path,
|
|
53
|
+
snapshot: str | Path,
|
|
54
|
+
*,
|
|
55
|
+
merge: bool = False,
|
|
56
|
+
) -> None:
|
|
57
|
+
# Restore collection data from a snapshot file (.json or .json.gz)
|
|
58
|
+
db_dir = Path(db_dir)
|
|
59
|
+
snap_path = Path(snapshot)
|
|
60
|
+
|
|
61
|
+
if snap_path.suffix == ".gz":
|
|
62
|
+
with gzip.GzipFile(snap_path, "rb") as f:
|
|
63
|
+
raw = f.read()
|
|
64
|
+
else:
|
|
65
|
+
raw = snap_path.read_bytes()
|
|
66
|
+
|
|
67
|
+
data: dict[str, list[dict]] = json.loads(raw)
|
|
68
|
+
|
|
69
|
+
for name, docs in data.items():
|
|
70
|
+
col_path = db_dir / f"{name}_table.json"
|
|
71
|
+
existing = []
|
|
72
|
+
if merge and col_path.exists():
|
|
73
|
+
existing = (json.loads(col_path.read_bytes()) if col_path.stat().st_size else [])
|
|
74
|
+
merged = existing + docs if merge else docs
|
|
75
|
+
|
|
76
|
+
# atomic write
|
|
77
|
+
tmp = col_path.with_suffix(".tmp")
|
|
78
|
+
tmp.write_text(json.dumps(merged, indent=2), encoding="utf-8")
|
|
79
|
+
os.replace(tmp, col_path)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def list_snapshots(backup_dir: str | Path) -> list[Path]:
|
|
83
|
+
# Return snapshots sorted newest first
|
|
84
|
+
d = Path(backup_dir)
|
|
85
|
+
if not d.exists():
|
|
86
|
+
return []
|
|
87
|
+
files = sorted(p for p in d.iterdir() if p.suffix in (".json", ".gz") and p.stem != ".tmp")
|
|
88
|
+
return list(reversed(files))
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def clean(backup_dir: str | Path, *, keep: int = 10, dry_run: bool = False) -> list[Path]:
|
|
92
|
+
# Remove old snapshots, keeping the `keep` most recent
|
|
93
|
+
snaps = list_snapshots(backup_dir)
|
|
94
|
+
if len(snaps) <= keep:
|
|
95
|
+
return []
|
|
96
|
+
to_remove = snaps[keep:]
|
|
97
|
+
if not dry_run:
|
|
98
|
+
for p in to_remove:
|
|
99
|
+
p.unlink()
|
|
100
|
+
return to_remove
|