xitzin 0.2.0__py3-none-any.whl → 0.4.0__py3-none-any.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.
xitzin/testing.py CHANGED
@@ -13,6 +13,7 @@ from typing import TYPE_CHECKING, Generator
13
13
  from urllib.parse import quote_plus
14
14
 
15
15
  from nauyaca.protocol.request import GeminiRequest
16
+ from nauyaca.protocol.request import TitanRequest as NauyacaTitanRequest
16
17
  from nauyaca.protocol.response import GeminiResponse
17
18
 
18
19
  if TYPE_CHECKING:
@@ -153,10 +154,15 @@ class TestClient:
153
154
  # Handle request through the app
154
155
  response = self._handle_sync(request)
155
156
 
157
+ # Convert bytes body to str for TestResponse
158
+ body = response.body
159
+ if isinstance(body, bytes):
160
+ body = body.decode("utf-8")
161
+
156
162
  return TestResponse(
157
163
  status=response.status,
158
164
  meta=response.meta,
159
- body=response.body,
165
+ body=body,
160
166
  )
161
167
 
162
168
  def get_input(
@@ -210,6 +216,100 @@ class TestClient:
210
216
  new_client._default_fingerprint = fingerprint
211
217
  return new_client
212
218
 
219
+ def upload(
220
+ self,
221
+ path: str,
222
+ content: bytes | str,
223
+ *,
224
+ mime_type: str = "text/gemini",
225
+ token: str | None = None,
226
+ cert_fingerprint: str | None = None,
227
+ ) -> TestResponse:
228
+ """Make a Titan upload request.
229
+
230
+ Args:
231
+ path: The request path (e.g., "/upload/file.gmi").
232
+ content: Content to upload (str will be UTF-8 encoded).
233
+ mime_type: Content MIME type (default: text/gemini).
234
+ token: Authentication token (if required by route).
235
+ cert_fingerprint: Mock client certificate fingerprint.
236
+
237
+ Returns:
238
+ TestResponse with status, meta, and body.
239
+
240
+ Example:
241
+ response = client.upload(
242
+ "/files/test.gmi",
243
+ "# Hello World",
244
+ mime_type="text/gemini",
245
+ token="secret123"
246
+ )
247
+ assert response.is_success
248
+ """
249
+ # Convert str to bytes
250
+ if isinstance(content, str):
251
+ content_bytes = content.encode("utf-8")
252
+ else:
253
+ content_bytes = content
254
+
255
+ # Build Titan URL
256
+ size = len(content_bytes)
257
+ url = f"titan://testserver{path};size={size};mime={mime_type}"
258
+ if token:
259
+ url += f";token={token}"
260
+
261
+ # Create TitanRequest
262
+ request = NauyacaTitanRequest.from_line(url)
263
+ request.content = content_bytes
264
+
265
+ # Set certificate info
266
+ fingerprint = cert_fingerprint or self._default_fingerprint
267
+ if fingerprint:
268
+ request.client_cert_fingerprint = fingerprint
269
+
270
+ # Handle through app
271
+ response = self._handle_titan_sync(request)
272
+
273
+ # Convert bytes body to str for TestResponse
274
+ body = response.body
275
+ if isinstance(body, bytes):
276
+ body = body.decode("utf-8")
277
+
278
+ return TestResponse(
279
+ status=response.status,
280
+ meta=response.meta,
281
+ body=body,
282
+ )
283
+
284
+ def delete(
285
+ self,
286
+ path: str,
287
+ *,
288
+ token: str | None = None,
289
+ cert_fingerprint: str | None = None,
290
+ ) -> TestResponse:
291
+ """Make a Titan delete request (zero-byte upload).
292
+
293
+ Args:
294
+ path: The request path to delete.
295
+ token: Authentication token (if required by route).
296
+ cert_fingerprint: Mock client certificate fingerprint.
297
+
298
+ Returns:
299
+ TestResponse with status, meta, and body.
300
+
301
+ Example:
302
+ response = client.delete("/files/old.gmi", token="secret123")
303
+ assert response.is_success
304
+ """
305
+ return self.upload(
306
+ path,
307
+ b"",
308
+ mime_type="text/gemini",
309
+ token=token,
310
+ cert_fingerprint=cert_fingerprint,
311
+ )
312
+
213
313
  def _handle_sync(self, request: GeminiRequest) -> GeminiResponse:
214
314
  """Handle a request synchronously."""
215
315
  try:
@@ -220,6 +320,16 @@ class TestClient:
220
320
 
221
321
  return loop.run_until_complete(self._app._handle_request(request))
222
322
 
323
+ def _handle_titan_sync(self, request: NauyacaTitanRequest) -> GeminiResponse:
324
+ """Handle a Titan request synchronously."""
325
+ try:
326
+ loop = asyncio.get_event_loop()
327
+ except RuntimeError:
328
+ loop = asyncio.new_event_loop()
329
+ asyncio.set_event_loop(loop)
330
+
331
+ return loop.run_until_complete(self._app._handle_titan_request(request))
332
+
223
333
 
224
334
  @contextmanager
225
335
  def test_app(app: "Xitzin") -> Generator[TestClient, None, None]:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: xitzin
3
- Version: 0.2.0
3
+ Version: 0.4.0
4
4
  Summary: A Gemini Application Framework
5
5
  Keywords: gemini,protocol,framework,async,geminispace
6
6
  Author: Alan Velasco
@@ -20,15 +20,18 @@ Classifier: Typing :: Typed
20
20
  Requires-Dist: jinja2>=3.1.0
21
21
  Requires-Dist: nauyaca>=0.3.2
22
22
  Requires-Dist: rich>=14.2.0
23
+ Requires-Dist: structlog>=25.5.0
23
24
  Requires-Dist: typing-extensions>=4.15.0
24
25
  Requires-Dist: sqlmodel>=0.0.22 ; extra == 'sqlmodel'
26
+ Requires-Dist: croniter>=1.0.0 ; extra == 'tasks'
25
27
  Requires-Python: >=3.10
26
- Project-URL: Changelog, https://xitzin.readthedocs.io/changelog/
27
- Project-URL: Documentation, https://xitzin.readthedocs.io
28
28
  Project-URL: Homepage, https://github.com/alanbato/xitzin
29
- Project-URL: Issues, https://github.com/alanbato/xitzin/issues
29
+ Project-URL: Documentation, https://xitzin.readthedocs.io
30
30
  Project-URL: Repository, https://github.com/alanbato/xitzin.git
31
+ Project-URL: Issues, https://github.com/alanbato/xitzin/issues
32
+ Project-URL: Changelog, https://xitzin.readthedocs.io/changelog/
31
33
  Provides-Extra: sqlmodel
34
+ Provides-Extra: tasks
32
35
  Description-Content-Type: text/markdown
33
36
 
34
37
  # Xitzin
@@ -0,0 +1,18 @@
1
+ xitzin/__init__.py,sha256=GMfAmzBJ6TMZJWfF38r1k0y-vswOdLw9xfWpPfYMcqc,1851
2
+ xitzin/application.py,sha256=axL5G4lRz0KTNLsNQkCGxWD0MGDpIH6Xe9OKkxJlJw8,27851
3
+ xitzin/auth.py,sha256=hHhrP_fYToY6J6CCU8vgAsWbYn6K16LUxkeDMzZTF2Q,5304
4
+ xitzin/cgi.py,sha256=nggSuQ0gXIKdBKWHqH_CNZ6UlVGaB3VAOu3ZRPc43ek,18046
5
+ xitzin/exceptions.py,sha256=82z-CjyC0FwFbo9hGTjjmurlL_Vd4rTVdgkmQoFLXT0,3883
6
+ xitzin/middleware.py,sha256=dZhrbOJPCrrbMiwrbRzxElsC7UDrWc7lvnQ5c0xBHts,12542
7
+ xitzin/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ xitzin/requests.py,sha256=1mbm5F8oywLPNpSdh2inn7K1IEeI3H5meOwj8jldi1Q,7783
9
+ xitzin/responses.py,sha256=QVH4B-qVHP7hxi0hIrOJhdCiHDf4PDCpSZ1fAD8iNkk,6525
10
+ xitzin/routing.py,sha256=8Kl3FV6EWZ6nW0bDTqJ-qysNyJ1iSRtCKicCAai8Lmk,18093
11
+ xitzin/scgi.py,sha256=t-ixEwrQR-bIRj56DkzBsvGyN75E5R0gasIjMfq5LFY,13665
12
+ xitzin/sqlmodel.py,sha256=lZvDzYAgnG8S2K-EYnx6PkO7D68pjM06uQLSKUZ9yY4,4500
13
+ xitzin/tasks.py,sha256=_smEXy-THge8wmQqWDtX3iUmAmoHniw_qqZBlKjCdqA,4597
14
+ xitzin/templating.py,sha256=spjxb05wgwKA4txOMbWJuwEVMaoLovo13_ukbXAcBDY,6040
15
+ xitzin/testing.py,sha256=wMiToopD2TAqTsreVqOrIUNCayORNQQennxR_GwMBSY,10980
16
+ xitzin-0.4.0.dist-info/WHEEL,sha256=XV0cjMrO7zXhVAIyyc8aFf1VjZ33Fen4IiJk5zFlC3g,80
17
+ xitzin-0.4.0.dist-info/METADATA,sha256=Zv-Rde7LWIcf_FQfclXu77BzGipWf6_n0fOXVzkvk0A,3456
18
+ xitzin-0.4.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: uv 0.9.21
2
+ Generator: uv 0.9.26
3
3
  Root-Is-Purelib: true
4
- Tag: py3-none-any
4
+ Tag: py3-none-any
@@ -1,16 +0,0 @@
1
- xitzin/__init__.py,sha256=9zCk_h4rI-QGLE3q95NSgPha-dtvSOEdwiZuTQmbBR4,1637
2
- xitzin/application.py,sha256=GWcL-u1C6xiUwYwMxg60kQxprJ_KaLcJFPcIBf8xCzU,17909
3
- xitzin/auth.py,sha256=KT1WprT4qF1u03T8lAGO_UzQBLQcg-OegIFubay7VlA,4511
4
- xitzin/cgi.py,sha256=nmKaeLwYfk3esRnxQnn1Rx-6EHKq2zL-Xvpw5hdI-rk,17627
5
- xitzin/exceptions.py,sha256=JdxAXHtnXMf6joXeFQMfxlcdbv_R5odb-qYPMbgRTZY,3382
6
- xitzin/middleware.py,sha256=z2QJJjMEinz5e4NeZvjsCeyMLt8ZD2a3Ox2V2vuTnk8,6748
7
- xitzin/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
- xitzin/requests.py,sha256=EeDqh0roz7eTItqarEDj53iFxMZlVsEIcimybKdqAHI,4612
9
- xitzin/responses.py,sha256=N4HeP9Yy2PIHY0zsGa2pj8xvX2OFHAjtqR5nogNh8UQ,6545
10
- xitzin/routing.py,sha256=SiT9J617GfQR_rPDD3Ivw0_N4CTh8-Jb_AZjWJnT5sw,12409
11
- xitzin/sqlmodel.py,sha256=lZvDzYAgnG8S2K-EYnx6PkO7D68pjM06uQLSKUZ9yY4,4500
12
- xitzin/templating.py,sha256=spjxb05wgwKA4txOMbWJuwEVMaoLovo13_ukbXAcBDY,6040
13
- xitzin/testing.py,sha256=JO41TeIJeb1CqHVqBOjCVAvv9BOlvDJYzAeG83ZofdE,7572
14
- xitzin-0.2.0.dist-info/WHEEL,sha256=RRVLqVugUmFOqBedBFAmA4bsgFcROUBiSUKlERi0Hcg,79
15
- xitzin-0.2.0.dist-info/METADATA,sha256=Jvf0vYB5goyuimrOa4321LhntdLNJKxIJCUpuIyTmbQ,3351
16
- xitzin-0.2.0.dist-info/RECORD,,