fastapi-extended-query-method 0.0.3__tar.gz → 0.0.3.31__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 (16) hide show
  1. {fastapi_extended_query_method-0.0.3 → fastapi_extended_query_method-0.0.3.31}/PKG-INFO +5 -5
  2. {fastapi_extended_query_method-0.0.3 → fastapi_extended_query_method-0.0.3.31}/README.md +4 -4
  3. {fastapi_extended_query_method-0.0.3 → fastapi_extended_query_method-0.0.3.31}/fastapi_extended_query_method.egg-info/PKG-INFO +5 -5
  4. {fastapi_extended_query_method-0.0.3 → fastapi_extended_query_method-0.0.3.31}/fastapi_extended_query_method.egg-info/SOURCES.txt +4 -1
  5. fastapi_extended_query_method-0.0.3.31/fastapi_extended_query_method.egg-info/top_level.txt +2 -0
  6. {fastapi_extended_query_method-0.0.3 → fastapi_extended_query_method-0.0.3.31}/setup.py +2 -2
  7. fastapi_extended_query_method-0.0.3.31/validate_data/main.py +49 -0
  8. fastapi_extended_query_method-0.0.3.31/validate_data/test_api_query_method.py +138 -0
  9. fastapi_extended_query_method-0.0.3.31/validate_data/test_cache_comparison.py +265 -0
  10. fastapi_extended_query_method-0.0.3/fastapi_extended_query_method.egg-info/top_level.txt +0 -1
  11. {fastapi_extended_query_method-0.0.3 → fastapi_extended_query_method-0.0.3.31}/LICENSE +0 -0
  12. {fastapi_extended_query_method-0.0.3 → fastapi_extended_query_method-0.0.3.31}/fastapi_extended_query_method.egg-info/dependency_links.txt +0 -0
  13. {fastapi_extended_query_method-0.0.3 → fastapi_extended_query_method-0.0.3.31}/fastapi_extended_query_method.egg-info/requires.txt +0 -0
  14. {fastapi_extended_query_method-0.0.3 → fastapi_extended_query_method-0.0.3.31}/setup.cfg +0 -0
  15. {fastapi_extended_query_method-0.0.3 → fastapi_extended_query_method-0.0.3.31}/src/__init__.py +0 -0
  16. {fastapi_extended_query_method-0.0.3 → fastapi_extended_query_method-0.0.3.31}/src/fastapi_extended_query_method.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastapi-extended-query-method
3
- Version: 0.0.3
3
+ Version: 0.0.3.31
4
4
  Summary: Native HTTP QUERY method support for FastAPI with automatic cache control and Swagger compatibility.
5
5
  Home-page: https://github.com/jorgecardona/fastapi-extended-query-method
6
6
  Author: Jorge Cardona
@@ -79,7 +79,7 @@ Use the **POST** operation in Swagger only for interactive testing.
79
79
  Real clients should invoke the QUERY method directly.
80
80
 
81
81
  <p align="center">
82
- <img src="images/swagger.png" alt="Swagger Interface" width="900">
82
+ <img src="https://raw.githubusercontent.com/JorgeCardona/fastapi-extended-query-method/refs/heads/main/images/swagger.png" alt="Swagger Interface" width="900">
83
83
  </p>
84
84
 
85
85
  ## Cache
@@ -99,7 +99,7 @@ python validate_data/test_api_query_method.py
99
99
  ```
100
100
 
101
101
  <p align="center">
102
- <img src="images/api_results.png" alt="API Results" width="900">
102
+ <img src="https://raw.githubusercontent.com/JorgeCardona/fastapi-extended-query-method/refs/heads/main/images/api_results.png" alt="API Results" width="900">
103
103
  </p>
104
104
 
105
105
  ## Testing Cache
@@ -109,12 +109,12 @@ python validate_data/test_api_query_method.py
109
109
  python validate_data/test_cache_comparison.py
110
110
  ```
111
111
  <p align="center">
112
- <img src="images/cache.png" alt="Stored Cache" width="900">
112
+ <img src="https://raw.githubusercontent.com/JorgeCardona/fastapi-extended-query-method/refs/heads/main/images/cache.png" alt="Stored Cache" width="900">
113
113
  </p>
114
114
 
115
115
  ### NO Using Cache Stored
116
116
  <p align="center">
117
- <img src="images/no_cache.png" alt="Without Cache" width="900">
117
+ <img src="https://raw.githubusercontent.com/JorgeCardona/fastapi-extended-query-method/refs/heads/main/images/no_cache.png" alt="Without Cache" width="900">
118
118
  </p>
119
119
 
120
120
  ## License
@@ -40,7 +40,7 @@ Use the **POST** operation in Swagger only for interactive testing.
40
40
  Real clients should invoke the QUERY method directly.
41
41
 
42
42
  <p align="center">
43
- <img src="images/swagger.png" alt="Swagger Interface" width="900">
43
+ <img src="https://raw.githubusercontent.com/JorgeCardona/fastapi-extended-query-method/refs/heads/main/images/swagger.png" alt="Swagger Interface" width="900">
44
44
  </p>
45
45
 
46
46
  ## Cache
@@ -60,7 +60,7 @@ python validate_data/test_api_query_method.py
60
60
  ```
61
61
 
62
62
  <p align="center">
63
- <img src="images/api_results.png" alt="API Results" width="900">
63
+ <img src="https://raw.githubusercontent.com/JorgeCardona/fastapi-extended-query-method/refs/heads/main/images/api_results.png" alt="API Results" width="900">
64
64
  </p>
65
65
 
66
66
  ## Testing Cache
@@ -70,12 +70,12 @@ python validate_data/test_api_query_method.py
70
70
  python validate_data/test_cache_comparison.py
71
71
  ```
72
72
  <p align="center">
73
- <img src="images/cache.png" alt="Stored Cache" width="900">
73
+ <img src="https://raw.githubusercontent.com/JorgeCardona/fastapi-extended-query-method/refs/heads/main/images/cache.png" alt="Stored Cache" width="900">
74
74
  </p>
75
75
 
76
76
  ### NO Using Cache Stored
77
77
  <p align="center">
78
- <img src="images/no_cache.png" alt="Without Cache" width="900">
78
+ <img src="https://raw.githubusercontent.com/JorgeCardona/fastapi-extended-query-method/refs/heads/main/images/no_cache.png" alt="Without Cache" width="900">
79
79
  </p>
80
80
 
81
81
  ## License
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastapi-extended-query-method
3
- Version: 0.0.3
3
+ Version: 0.0.3.31
4
4
  Summary: Native HTTP QUERY method support for FastAPI with automatic cache control and Swagger compatibility.
5
5
  Home-page: https://github.com/jorgecardona/fastapi-extended-query-method
6
6
  Author: Jorge Cardona
@@ -79,7 +79,7 @@ Use the **POST** operation in Swagger only for interactive testing.
79
79
  Real clients should invoke the QUERY method directly.
80
80
 
81
81
  <p align="center">
82
- <img src="images/swagger.png" alt="Swagger Interface" width="900">
82
+ <img src="https://raw.githubusercontent.com/JorgeCardona/fastapi-extended-query-method/refs/heads/main/images/swagger.png" alt="Swagger Interface" width="900">
83
83
  </p>
84
84
 
85
85
  ## Cache
@@ -99,7 +99,7 @@ python validate_data/test_api_query_method.py
99
99
  ```
100
100
 
101
101
  <p align="center">
102
- <img src="images/api_results.png" alt="API Results" width="900">
102
+ <img src="https://raw.githubusercontent.com/JorgeCardona/fastapi-extended-query-method/refs/heads/main/images/api_results.png" alt="API Results" width="900">
103
103
  </p>
104
104
 
105
105
  ## Testing Cache
@@ -109,12 +109,12 @@ python validate_data/test_api_query_method.py
109
109
  python validate_data/test_cache_comparison.py
110
110
  ```
111
111
  <p align="center">
112
- <img src="images/cache.png" alt="Stored Cache" width="900">
112
+ <img src="https://raw.githubusercontent.com/JorgeCardona/fastapi-extended-query-method/refs/heads/main/images/cache.png" alt="Stored Cache" width="900">
113
113
  </p>
114
114
 
115
115
  ### NO Using Cache Stored
116
116
  <p align="center">
117
- <img src="images/no_cache.png" alt="Without Cache" width="900">
117
+ <img src="https://raw.githubusercontent.com/JorgeCardona/fastapi-extended-query-method/refs/heads/main/images/no_cache.png" alt="Without Cache" width="900">
118
118
  </p>
119
119
 
120
120
  ## License
@@ -7,4 +7,7 @@ fastapi_extended_query_method.egg-info/dependency_links.txt
7
7
  fastapi_extended_query_method.egg-info/requires.txt
8
8
  fastapi_extended_query_method.egg-info/top_level.txt
9
9
  src/__init__.py
10
- src/fastapi_extended_query_method.py
10
+ src/fastapi_extended_query_method.py
11
+ validate_data/main.py
12
+ validate_data/test_api_query_method.py
13
+ validate_data/test_cache_comparison.py
@@ -15,7 +15,7 @@ setuptools.setup(
15
15
  long_description=long_description,
16
16
  long_description_content_type="text/markdown",
17
17
  url="https://github.com/jorgecardona/fastapi-extended-query-method",
18
- packages=setuptools.find_packages(),
18
+ packages=["src", "validate_data"],
19
19
  include_package_data=True,
20
20
  install_requires=[
21
21
  "fastapi>=0.139.0",
@@ -50,4 +50,4 @@ setuptools.setup(
50
50
  "middleware",
51
51
  ],
52
52
  python_requires=">=3.10",
53
- )
53
+ )
@@ -0,0 +1,49 @@
1
+ import os
2
+ import uuid
3
+
4
+ from fastapi.responses import JSONResponse
5
+
6
+ from fastapi import Query
7
+
8
+ from src.fastapi_extended_query_method import FastAPIWithQueryHttpMethod
9
+ from validate_data.utils.models import SearchFilters, SearchResponse
10
+ from validate_data.utils.db_example_config import get_products_from_sqlite
11
+
12
+ app = FastAPIWithQueryHttpMethod(
13
+ query_saving_cache=os.getenv("QUERY_SAVING_CACHE", "true").lower() == "true"
14
+ )
15
+
16
+
17
+ @app.query("/products/filter", response_model=SearchResponse)
18
+ async def filter_products(
19
+ filters: SearchFilters,
20
+ limit: int = Query(default=10, ge=1),
21
+ order_by: str = "id",
22
+ ):
23
+ filtered_products = get_products_from_sqlite(
24
+ filters=filters,
25
+ limit=limit,
26
+ order_by=order_by,
27
+ )
28
+
29
+ execution_id = str(uuid.uuid4())
30
+
31
+ return JSONResponse(
32
+ content={
33
+ "status": "success",
34
+ "execution_id": execution_id,
35
+ "total_found": len(filtered_products),
36
+ "products": filtered_products,
37
+ },
38
+ headers={
39
+ "X-Execution-Id": execution_id,
40
+ },
41
+ )
42
+
43
+
44
+ @app.get("/health")
45
+ def health_check():
46
+ return {
47
+ "status": "ok",
48
+ "query_saving_cache": app.query_saving_cache,
49
+ }
@@ -0,0 +1,138 @@
1
+ # python .\validate_data\test_api_query_method.py
2
+
3
+ import json
4
+ from pathlib import Path
5
+ import http.client
6
+
7
+ from utils.functions import (
8
+ start_server,
9
+ stop_server,
10
+ wait_for_server,
11
+ print_server_log,
12
+ render_table,
13
+ cleanup_test_files,
14
+ PAYLOAD,
15
+ BASE_DIR,
16
+ QUERY_PATH,
17
+ HOST,
18
+ PORT,
19
+ )
20
+
21
+
22
+ BASE_DIR = Path(__file__).resolve().parent
23
+ PROJECT_ROOT = BASE_DIR.parents[0]
24
+
25
+
26
+ def main():
27
+ server_process = None
28
+
29
+ try:
30
+ print("\n" + "=" * 80)
31
+ print("STARTING SERVER")
32
+ print("=" * 80)
33
+
34
+ server_process = start_server()
35
+
36
+ if not wait_for_server():
37
+ print("❌ El servidor no respondió a tiempo")
38
+ print_server_log()
39
+ return
40
+
41
+ print("✅ Server Ready")
42
+
43
+ print("\n" + "=" * 80)
44
+ print("QUERY REQUEST")
45
+ print("=" * 80)
46
+ print(f"Method : QUERY")
47
+ print(f"URL : http://{HOST}:{PORT}{QUERY_PATH}")
48
+ print(f"Headers : Content-Type: application/json")
49
+ print(f"Body : {json.dumps(PAYLOAD, ensure_ascii=False, indent=2)}")
50
+ print("=" * 80)
51
+
52
+ connection = http.client.HTTPConnection(HOST, PORT, timeout=10)
53
+
54
+ headers = {
55
+ "Content-Type": "application/json"
56
+ }
57
+
58
+ body = json.dumps(PAYLOAD, ensure_ascii=False)
59
+
60
+ connection.request(
61
+ "QUERY",
62
+ QUERY_PATH,
63
+ body=body,
64
+ headers=headers
65
+ )
66
+
67
+ response = connection.getresponse()
68
+ raw_data = response.read().decode("utf-8", errors="ignore")
69
+ connection.close()
70
+
71
+ try:
72
+ response_json = json.loads(raw_data)
73
+ except json.JSONDecodeError:
74
+ print("❌ La respuesta no es un JSON válido")
75
+ print(raw_data)
76
+ return
77
+
78
+ products = response_json.get("products", [])
79
+
80
+ print("\n" + "=" * 80)
81
+ print("QUERY RESPONSE")
82
+ print("=" * 80)
83
+
84
+ print(f"HTTP Status : {response.status}")
85
+ print(f"Reason : {response.reason}")
86
+ print(f"Status : {response_json.get('status')}")
87
+ print(f"Execution Id : {response_json.get('execution_id')}")
88
+ print(f"Total Found : {response_json.get('total_found')}")
89
+
90
+ rows = []
91
+
92
+ for product in products:
93
+ rows.append([
94
+ product.get("id", "-"),
95
+ product.get("name", "-"),
96
+ product.get("category", "-"),
97
+ product.get("price", "-"),
98
+ product.get("brand", "-"),
99
+ ])
100
+
101
+ print("\n" + "=" * 80)
102
+ print("PRODUCTS")
103
+ print("=" * 80)
104
+
105
+ render_table(
106
+ rows,
107
+ [
108
+ "Id",
109
+ "Name",
110
+ "Category",
111
+ "Price",
112
+ "Brand",
113
+ ],
114
+ )
115
+
116
+ print("\n" + "=" * 80)
117
+ print("SUMMARY")
118
+ print("=" * 80)
119
+ print(f"Products returned : {len(products)}")
120
+ print("Result : PASS" if response.status == 200 else "Result : FAIL")
121
+
122
+ except KeyboardInterrupt:
123
+ print("\n⚠️ Prueba interrumpida por el usuario")
124
+
125
+ except Exception as e:
126
+ print(f"\n❌ Error ejecutando la prueba: {e}")
127
+ print_server_log()
128
+
129
+ finally:
130
+ stop_server(server_process)
131
+ print("\n🧹 Server Stopped")
132
+
133
+ # Limpiar archivos temporales al finalizar
134
+ cleanup_test_files(auto_cleanup=True)
135
+
136
+
137
+ if __name__ == "__main__":
138
+ main()
@@ -0,0 +1,265 @@
1
+ # python .\validate_data\test_cache_comparison.py
2
+
3
+ import time
4
+ import requests_cache
5
+ from utils.functions import (
6
+ start_server,
7
+ stop_server,
8
+ wait_for_server,
9
+ print_server_log,
10
+ render_table,
11
+ cleanup_test_files,
12
+ URL,
13
+ PAYLOAD,
14
+ BASE_DIR,
15
+ )
16
+
17
+ CACHE_DIR = BASE_DIR / "cache_test_files"
18
+ CACHE_DIR.mkdir(parents=True, exist_ok=True)
19
+
20
+ def print_cache_status(session):
21
+ """
22
+ [ES]
23
+ Imprime el estado actual del caché.
24
+
25
+ [EN]
26
+ Prints the current cache status.
27
+ """
28
+
29
+ cache_entries = list(session.cache.responses.keys())
30
+
31
+ print("\nEstado del caché")
32
+ print("-" * 70)
33
+ print(f"Entradas almacenadas : {len(cache_entries)}")
34
+
35
+ if cache_entries:
36
+ print("Claves almacenadas:")
37
+ for index, key in enumerate(cache_entries, start=1):
38
+ print(f" {index}. {key}")
39
+ else:
40
+ print("No existen respuestas almacenadas.")
41
+
42
+
43
+ def test_cache_behavior(query_saving_cache: bool) -> bool:
44
+ """
45
+ [ES]
46
+ Ejecuta tres peticiones QUERY y presenta una tabla resumen por cada
47
+ solicitud. Si verbose=True, también imprime el log detallado completo.
48
+
49
+ [EN]
50
+ Executes three QUERY requests and presents a summary table for each
51
+ request. If verbose=True, it also prints the full detailed log.
52
+ """
53
+
54
+ print("\n" + "=" * 80)
55
+ print("CACHE BEHAVIOR TEST")
56
+ print("=" * 80)
57
+ print(f"QUERY_SAVING_CACHE = {query_saving_cache}")
58
+
59
+ if not query_saving_cache:
60
+ print("\nEXPECTED RESULT")
61
+ print("-" * 80)
62
+ print("• Cache-Control = no-store")
63
+ print("• Every request must hit the SERVER.")
64
+ print("• Nothing should ever be stored in the cache.")
65
+ else:
66
+ print("\nEXPECTED RESULT")
67
+ print("-" * 80)
68
+ print("• Cache-Control header should not be present.")
69
+ print("• First request should come from the SERVER.")
70
+ print("• First response should be stored.")
71
+ print("• Remaining requests should come from CACHE.")
72
+
73
+ session = requests_cache.CachedSession(
74
+ cache_name=str(CACHE_DIR / "test_query_method_request_cache"),
75
+ backend="sqlite",
76
+ allowable_methods=("GET", "HEAD", "QUERY"),
77
+ cache_control=True,
78
+ )
79
+
80
+ session.cache.clear()
81
+
82
+ initial_entries = len(session.cache.responses)
83
+ expected_cache_header = (
84
+ "no-store, no-cache, must-revalidate, max-age=0"
85
+ if not query_saving_cache
86
+ else None
87
+ )
88
+
89
+ print("\n" + "=" * 80)
90
+ print("INITIAL CACHE STATE")
91
+ print("=" * 80)
92
+ print(f"Cached responses : {initial_entries}")
93
+ if initial_entries:
94
+ print("Cache is not empty.")
95
+ else:
96
+ print("Cache is empty.")
97
+
98
+ passed = True
99
+ report_rows = []
100
+
101
+ for request_number in range(1, 4):
102
+ before_entries = len(session.cache.responses)
103
+
104
+ response = session.request(
105
+ method="QUERY",
106
+ url=URL,
107
+ json=PAYLOAD,
108
+ timeout=10,
109
+ )
110
+
111
+ after_entries = len(session.cache.responses)
112
+
113
+ from_cache = getattr(response, "from_cache", False)
114
+ cache_control = response.headers.get("Cache-Control")
115
+
116
+ expected_from_cache = (
117
+ False if not query_saving_cache else request_number != 1
118
+ )
119
+
120
+ cache_ok = cache_control == expected_cache_header
121
+ source_ok = from_cache == expected_from_cache
122
+ status_ok = response.status_code == 200
123
+
124
+ request_passed = cache_ok and source_ok and status_ok
125
+ passed &= request_passed
126
+
127
+ cache_keys = list(session.cache.responses.keys())
128
+ cache_key = cache_keys[0] if cache_keys else "-"
129
+
130
+ if after_entries > before_entries:
131
+ result_text = "Stored"
132
+ elif from_cache:
133
+ result_text = "Reused"
134
+ else:
135
+ result_text = "No cache"
136
+
137
+ report_rows.append([
138
+ str(query_saving_cache),
139
+ request_number,
140
+ response.status_code,
141
+ "CACHE" if from_cache else "SERVER",
142
+ cache_control or "<not present>",
143
+ "CACHE" if expected_from_cache else "SERVER",
144
+ "PASS" if status_ok else "FAIL",
145
+ "PASS" if source_ok else "FAIL",
146
+ "PASS" if cache_ok else "FAIL",
147
+ len(cache_keys),
148
+ cache_key,
149
+ result_text,
150
+ ])
151
+
152
+ print_report_summary(session, report_rows, query_saving_cache)
153
+
154
+ return passed
155
+
156
+ def print_report_summary(session, report_rows, query_saving_cache):
157
+
158
+ final_entries = len(session.cache.responses)
159
+
160
+ print("\n" + "=" * 80)
161
+ print("REQUEST REPORT")
162
+ print("=" * 80)
163
+
164
+ headers = [
165
+ "query_saving_cache",
166
+ "Req",
167
+ "Status",
168
+ "Source",
169
+ "Cache-Control",
170
+ "Expected",
171
+ "Status ✓",
172
+ "Source ✓",
173
+ "Header ✓",
174
+ "Entries",
175
+ "Cache Key",
176
+ "Result",
177
+ ]
178
+
179
+ render_table(report_rows, headers)
180
+
181
+ print("\n" + "=" * 80)
182
+ print("SUMMARY")
183
+ print("=" * 80)
184
+
185
+ server_hits = sum(1 for row in report_rows if row[3] == "SERVER")
186
+ cache_hits = sum(1 for row in report_rows if row[3] == "CACHE")
187
+
188
+ print(f"Requests executed : 3")
189
+ print(f"Responses SERVER : {server_hits}")
190
+ print(f"Responses CACHE : {cache_hits}")
191
+ print(f"Cache entries : {final_entries}")
192
+
193
+ if not query_saving_cache:
194
+ print("Conclusion : No response was cached.")
195
+ print(" Cache saving is DISABLED.")
196
+ else:
197
+ print("Conclusion : First response was cached and reused.")
198
+ print(" Cache saving is ENABLED.")
199
+
200
+ print("\nFINAL CACHE STATE")
201
+ print("-" * 80)
202
+ print(f"Cached responses : {final_entries}")
203
+
204
+ if final_entries:
205
+ print("\nStored cache keys:")
206
+ for index, key in enumerate(session.cache.responses.keys(), start=1):
207
+ print(f"{index}. {key}")
208
+ else:
209
+ print("Cache is empty.")
210
+
211
+ print()
212
+
213
+ def main():
214
+ server_process = None
215
+
216
+ try:
217
+ configurations = [
218
+ (False, "query_saving_cache=False (No CACHE - NO Save data, No Reuse in Future requests)"),
219
+ (True, "query_saving_cache=True (Using CACHE - Save Data for Reuse in Future requests)"),
220
+ ]
221
+
222
+ for query_saving_cache, config_name in configurations:
223
+ print(f"\n{'=' * 70}")
224
+ print(f"Configuring: {config_name}")
225
+ print(f"{'=' * 70}")
226
+
227
+ stop_server(server_process)
228
+ server_process = start_server(query_saving_cache)
229
+
230
+ if wait_for_server():
231
+ print("✅ Server Ready")
232
+ test_cache_behavior(query_saving_cache)
233
+ else:
234
+ print("❌ El servidor no respondió a tiempo")
235
+ print_server_log()
236
+
237
+ time.sleep(1)
238
+
239
+ print(f"\n{'=' * 70}")
240
+ print("📋 RESULTS SUMMARY")
241
+ print("=" * 70)
242
+ print("query_saving_cache=False (SIN CACHE - NO GUARDA)")
243
+ print(" - Todas las peticiones deberían venir con from_cache=False")
244
+ print(" - Cache-Control debería bloquear caché del cliente")
245
+ print(" - NO se almacenan respuestas en caché")
246
+ print()
247
+ print("query_saving_cache=True (USING CACHE - SAVE DATA)")
248
+ print(" - La primera petición debería venir con from_cache=False")
249
+ print(" - Las siguientes podrían venir con from_cache=True")
250
+ print(" - SÍ se almacenan respuestas en caché")
251
+ print(" - Solo si el cliente respeta la caché")
252
+
253
+ except KeyboardInterrupt:
254
+ print("\n⚠️ Prueba interrumpida por el usuario")
255
+
256
+ finally:
257
+ stop_server(server_process)
258
+ print("\n🧹 Server Stopped")
259
+
260
+ # Limpiar archivos temporales al finalizar
261
+ cleanup_test_files(auto_cleanup=True)
262
+
263
+
264
+ if __name__ == "__main__":
265
+ main()