hypern 0.3.10__tar.gz → 0.3.12__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 (154) hide show
  1. hypern-0.3.12/.github/workflows/release-CI.yml +143 -0
  2. {hypern-0.3.10 → hypern-0.3.12}/.github/workflows/security-scan.yml +1 -0
  3. {hypern-0.3.10 → hypern-0.3.12}/Cargo.lock +1 -1
  4. {hypern-0.3.10 → hypern-0.3.12}/Cargo.toml +3 -2
  5. {hypern-0.3.10 → hypern-0.3.12}/PKG-INFO +1 -1
  6. {hypern-0.3.10 → hypern-0.3.12}/hypern/__init__.py +6 -0
  7. {hypern-0.3.10 → hypern-0.3.12}/hypern/application.py +5 -4
  8. {hypern-0.3.10 → hypern-0.3.12}/hypern/datastructures.py +0 -13
  9. {hypern-0.3.10 → hypern-0.3.12}/hypern/enum.py +12 -0
  10. {hypern-0.3.10 → hypern-0.3.12}/hypern/processpool.py +0 -2
  11. {hypern-0.3.10 → hypern-0.3.12}/hypern/response/response.py +1 -3
  12. {hypern-0.3.10 → hypern-0.3.12}/hypern/routing/parser.py +1 -1
  13. {hypern-0.3.10 → hypern-0.3.12}/hypern/routing/route.py +22 -12
  14. {hypern-0.3.10 → hypern-0.3.12}/pyproject.toml +2 -2
  15. {hypern-0.3.10 → hypern-0.3.12}/src/lib.rs +0 -2
  16. hypern-0.3.12/src/router/mod.rs +3 -0
  17. hypern-0.3.12/src/router/radix.rs +160 -0
  18. {hypern-0.3.10 → hypern-0.3.12}/src/router/route.rs +25 -5
  19. hypern-0.3.12/src/router/router.rs +143 -0
  20. {hypern-0.3.10 → hypern-0.3.12}/src/server.rs +7 -6
  21. {hypern-0.3.10 → hypern-0.3.12}/src/types/mod.rs +0 -2
  22. {hypern-0.3.10 → hypern-0.3.12}/src/types/request.rs +2 -2
  23. hypern-0.3.10/.github/workflows/release-CI.yml +0 -168
  24. hypern-0.3.10/hypern/background.py +0 -4
  25. hypern-0.3.10/hypern/scheduler.py +0 -5
  26. hypern-0.3.10/src/mem_pool.rs +0 -155
  27. hypern-0.3.10/src/router/mod.rs +0 -2
  28. hypern-0.3.10/src/router/router.rs +0 -237
  29. hypern-0.3.10/src/types/http.rs +0 -16
  30. hypern-0.3.10/src/types/url.rs +0 -24
  31. {hypern-0.3.10 → hypern-0.3.12}/.github/dependabot.yml +0 -0
  32. {hypern-0.3.10 → hypern-0.3.12}/.github/workflows/codeql.yml +0 -0
  33. {hypern-0.3.10 → hypern-0.3.12}/.github/workflows/codspeed.yml +0 -0
  34. {hypern-0.3.10 → hypern-0.3.12}/.github/workflows/preview-deployments.yml +0 -0
  35. {hypern-0.3.10 → hypern-0.3.12}/.gitignore +0 -0
  36. {hypern-0.3.10 → hypern-0.3.12}/.pre-commit-config.yaml +0 -0
  37. {hypern-0.3.10 → hypern-0.3.12}/CODE_OF_CONDUCT.md +0 -0
  38. {hypern-0.3.10 → hypern-0.3.12}/LICENSE +0 -0
  39. {hypern-0.3.10 → hypern-0.3.12}/README.md +0 -0
  40. {hypern-0.3.10 → hypern-0.3.12}/SECURITY.md +0 -0
  41. {hypern-0.3.10 → hypern-0.3.12}/benchmark.sh +0 -0
  42. {hypern-0.3.10 → hypern-0.3.12}/docs/background.md +0 -0
  43. {hypern-0.3.10 → hypern-0.3.12}/docs/cache.md +0 -0
  44. {hypern-0.3.10 → hypern-0.3.12}/docs/database.md +0 -0
  45. {hypern-0.3.10 → hypern-0.3.12}/docs/index.md +0 -0
  46. {hypern-0.3.10 → hypern-0.3.12}/docs/response.md +0 -0
  47. {hypern-0.3.10 → hypern-0.3.12}/docs/schedule.md +0 -0
  48. {hypern-0.3.10 → hypern-0.3.12}/docs/websocket.md +0 -0
  49. {hypern-0.3.10 → hypern-0.3.12}/hypern/args_parser.py +0 -0
  50. {hypern-0.3.10 → hypern-0.3.12}/hypern/auth/__init__.py +0 -0
  51. {hypern-0.3.10 → hypern-0.3.12}/hypern/auth/authorization.py +0 -0
  52. {hypern-0.3.10 → hypern-0.3.12}/hypern/caching/__init__.py +0 -0
  53. {hypern-0.3.10 → hypern-0.3.12}/hypern/caching/backend.py +0 -0
  54. {hypern-0.3.10 → hypern-0.3.12}/hypern/caching/redis_backend.py +0 -0
  55. {hypern-0.3.10 → hypern-0.3.12}/hypern/caching/strategies.py +0 -0
  56. {hypern-0.3.10 → hypern-0.3.12}/hypern/cli/__init__.py +0 -0
  57. {hypern-0.3.10 → hypern-0.3.12}/hypern/cli/commands.py +0 -0
  58. {hypern-0.3.10 → hypern-0.3.12}/hypern/config.py +0 -0
  59. {hypern-0.3.10 → hypern-0.3.12}/hypern/database/__init__.py +0 -0
  60. {hypern-0.3.10 → hypern-0.3.12}/hypern/database/sqlalchemy/__init__.py +0 -0
  61. {hypern-0.3.10 → hypern-0.3.12}/hypern/database/sqlalchemy/config.py +0 -0
  62. {hypern-0.3.10 → hypern-0.3.12}/hypern/database/sqlalchemy/repository.py +0 -0
  63. {hypern-0.3.10 → hypern-0.3.12}/hypern/database/sqlx/__init__.py +0 -0
  64. {hypern-0.3.10 → hypern-0.3.12}/hypern/database/sqlx/field.py +0 -0
  65. {hypern-0.3.10 → hypern-0.3.12}/hypern/database/sqlx/migrate.py +0 -0
  66. {hypern-0.3.10 → hypern-0.3.12}/hypern/database/sqlx/model.py +0 -0
  67. {hypern-0.3.10 → hypern-0.3.12}/hypern/database/sqlx/query.py +0 -0
  68. {hypern-0.3.10 → hypern-0.3.12}/hypern/exceptions/__init__.py +0 -0
  69. {hypern-0.3.10 → hypern-0.3.12}/hypern/exceptions/base.py +0 -0
  70. {hypern-0.3.10 → hypern-0.3.12}/hypern/exceptions/common.py +0 -0
  71. {hypern-0.3.10 → hypern-0.3.12}/hypern/exceptions/errors.py +0 -0
  72. {hypern-0.3.10 → hypern-0.3.12}/hypern/exceptions/formatters.py +0 -0
  73. {hypern-0.3.10 → hypern-0.3.12}/hypern/exceptions/http.py +0 -0
  74. {hypern-0.3.10 → hypern-0.3.12}/hypern/gateway/__init__.py +0 -0
  75. {hypern-0.3.10 → hypern-0.3.12}/hypern/gateway/aggregator.py +0 -0
  76. {hypern-0.3.10 → hypern-0.3.12}/hypern/gateway/gateway.py +0 -0
  77. {hypern-0.3.10 → hypern-0.3.12}/hypern/gateway/proxy.py +0 -0
  78. {hypern-0.3.10 → hypern-0.3.12}/hypern/gateway/service.py +0 -0
  79. {hypern-0.3.10 → hypern-0.3.12}/hypern/hypern.pyi +0 -0
  80. {hypern-0.3.10 → hypern-0.3.12}/hypern/i18n/__init__.py +0 -0
  81. {hypern-0.3.10 → hypern-0.3.12}/hypern/logging/__init__.py +0 -0
  82. {hypern-0.3.10 → hypern-0.3.12}/hypern/logging/logger.py +0 -0
  83. {hypern-0.3.10 → hypern-0.3.12}/hypern/middleware/__init__.py +0 -0
  84. {hypern-0.3.10 → hypern-0.3.12}/hypern/middleware/base.py +0 -0
  85. {hypern-0.3.10 → hypern-0.3.12}/hypern/middleware/cache.py +0 -0
  86. {hypern-0.3.10 → hypern-0.3.12}/hypern/middleware/compress.py +0 -0
  87. {hypern-0.3.10 → hypern-0.3.12}/hypern/middleware/cors.py +0 -0
  88. {hypern-0.3.10 → hypern-0.3.12}/hypern/middleware/i18n.py +0 -0
  89. {hypern-0.3.10 → hypern-0.3.12}/hypern/middleware/limit.py +0 -0
  90. {hypern-0.3.10 → hypern-0.3.12}/hypern/middleware/security.py +0 -0
  91. {hypern-0.3.10 → hypern-0.3.12}/hypern/openapi/__init__.py +0 -0
  92. {hypern-0.3.10 → hypern-0.3.12}/hypern/openapi/schemas.py +0 -0
  93. {hypern-0.3.10 → hypern-0.3.12}/hypern/openapi/swagger.py +0 -0
  94. {hypern-0.3.10 → hypern-0.3.12}/hypern/py.typed +0 -0
  95. {hypern-0.3.10 → hypern-0.3.12}/hypern/reload.py +0 -0
  96. {hypern-0.3.10 → hypern-0.3.12}/hypern/response/__init__.py +0 -0
  97. {hypern-0.3.10 → hypern-0.3.12}/hypern/routing/__init__.py +0 -0
  98. {hypern-0.3.10 → hypern-0.3.12}/hypern/routing/dispatcher.py +0 -0
  99. {hypern-0.3.10 → hypern-0.3.12}/hypern/routing/endpoint.py +0 -0
  100. {hypern-0.3.10 → hypern-0.3.12}/hypern/routing/queue.py +0 -0
  101. {hypern-0.3.10 → hypern-0.3.12}/hypern/worker.py +0 -0
  102. {hypern-0.3.10 → hypern-0.3.12}/hypern/ws/__init__.py +0 -0
  103. {hypern-0.3.10 → hypern-0.3.12}/hypern/ws/channel.py +0 -0
  104. {hypern-0.3.10 → hypern-0.3.12}/hypern/ws/heartbeat.py +0 -0
  105. {hypern-0.3.10 → hypern-0.3.12}/hypern/ws/room.py +0 -0
  106. {hypern-0.3.10 → hypern-0.3.12}/hypern/ws/route.py +0 -0
  107. {hypern-0.3.10 → hypern-0.3.12}/poetry.lock +0 -0
  108. {hypern-0.3.10 → hypern-0.3.12}/scripts/format.sh +0 -0
  109. {hypern-0.3.10 → hypern-0.3.12}/sonar-project.properties +0 -0
  110. {hypern-0.3.10 → hypern-0.3.12}/src/background/background_task.rs +0 -0
  111. {hypern-0.3.10 → hypern-0.3.12}/src/background/background_tasks.rs +0 -0
  112. {hypern-0.3.10 → hypern-0.3.12}/src/background/mod.rs +0 -0
  113. {hypern-0.3.10 → hypern-0.3.12}/src/database/context.rs +0 -0
  114. {hypern-0.3.10 → hypern-0.3.12}/src/database/mod.rs +0 -0
  115. {hypern-0.3.10 → hypern-0.3.12}/src/database/sql/config.rs +0 -0
  116. {hypern-0.3.10 → hypern-0.3.12}/src/database/sql/connection.rs +0 -0
  117. {hypern-0.3.10 → hypern-0.3.12}/src/database/sql/db_trait.rs +0 -0
  118. {hypern-0.3.10 → hypern-0.3.12}/src/database/sql/mod.rs +0 -0
  119. {hypern-0.3.10 → hypern-0.3.12}/src/database/sql/mysql.rs +0 -0
  120. {hypern-0.3.10 → hypern-0.3.12}/src/database/sql/postgresql.rs +0 -0
  121. {hypern-0.3.10 → hypern-0.3.12}/src/database/sql/sqlite.rs +0 -0
  122. {hypern-0.3.10 → hypern-0.3.12}/src/database/sql/transaction.rs +0 -0
  123. {hypern-0.3.10 → hypern-0.3.12}/src/di.rs +0 -0
  124. {hypern-0.3.10 → hypern-0.3.12}/src/executor.rs +0 -0
  125. {hypern-0.3.10 → hypern-0.3.12}/src/instants.rs +0 -0
  126. {hypern-0.3.10 → hypern-0.3.12}/src/middlewares/base.rs +0 -0
  127. {hypern-0.3.10 → hypern-0.3.12}/src/middlewares/mod.rs +0 -0
  128. {hypern-0.3.10 → hypern-0.3.12}/src/openapi/mod.rs +0 -0
  129. {hypern-0.3.10 → hypern-0.3.12}/src/openapi/schemas.rs +0 -0
  130. {hypern-0.3.10 → hypern-0.3.12}/src/openapi/swagger.rs +0 -0
  131. {hypern-0.3.10 → hypern-0.3.12}/src/scheduler/job.rs +0 -0
  132. {hypern-0.3.10 → hypern-0.3.12}/src/scheduler/mod.rs +0 -0
  133. {hypern-0.3.10 → hypern-0.3.12}/src/scheduler/retry.rs +0 -0
  134. {hypern-0.3.10 → hypern-0.3.12}/src/scheduler/scheduler.rs +0 -0
  135. {hypern-0.3.10 → hypern-0.3.12}/src/types/body.rs +0 -0
  136. {hypern-0.3.10 → hypern-0.3.12}/src/types/function_info.rs +0 -0
  137. {hypern-0.3.10 → hypern-0.3.12}/src/types/header.rs +0 -0
  138. {hypern-0.3.10 → hypern-0.3.12}/src/types/middleware.rs +0 -0
  139. {hypern-0.3.10 → hypern-0.3.12}/src/types/query.rs +0 -0
  140. {hypern-0.3.10 → hypern-0.3.12}/src/types/response.rs +0 -0
  141. {hypern-0.3.10 → hypern-0.3.12}/src/ws/mod.rs +0 -0
  142. {hypern-0.3.10 → hypern-0.3.12}/src/ws/route.rs +0 -0
  143. {hypern-0.3.10 → hypern-0.3.12}/src/ws/router.rs +0 -0
  144. {hypern-0.3.10 → hypern-0.3.12}/src/ws/socket.rs +0 -0
  145. {hypern-0.3.10 → hypern-0.3.12}/src/ws/websocket.rs +0 -0
  146. {hypern-0.3.10 → hypern-0.3.12}/tests/__init__.py +0 -0
  147. {hypern-0.3.10 → hypern-0.3.12}/tests/conftest.py +0 -0
  148. {hypern-0.3.10 → hypern-0.3.12}/tests/server.py +0 -0
  149. {hypern-0.3.10 → hypern-0.3.12}/tests/test_functional_handler.py +0 -0
  150. {hypern-0.3.10 → hypern-0.3.12}/tests/test_request_file.py +0 -0
  151. {hypern-0.3.10 → hypern-0.3.12}/tests/test_response_type.py +0 -0
  152. {hypern-0.3.10 → hypern-0.3.12}/tests/test_sync_async.py +0 -0
  153. {hypern-0.3.10 → hypern-0.3.12}/tests/test_validate_field.py +0 -0
  154. {hypern-0.3.10 → hypern-0.3.12}/tests/utils.py +0 -0
@@ -0,0 +1,143 @@
1
+ # This file is autogenerated by maturin v1.8.1
2
+ # To update, run
3
+ #
4
+ # maturin generate-ci -m Cargo.toml github
5
+ #
6
+ name: CI
7
+
8
+ on:
9
+ push:
10
+ tags:
11
+ - 'v*'
12
+ pull_request:
13
+ workflow_dispatch:
14
+
15
+ permissions:
16
+ contents: read
17
+
18
+ jobs:
19
+
20
+ linux:
21
+ runs-on: "ubuntu-latest"
22
+ strategy:
23
+ matrix:
24
+ target: [x86_64, x86, aarch64, armv7, s390x, ppc64le]
25
+ python-version: ["3.10", "3.11", "3.12"]
26
+ steps:
27
+ - uses: actions/checkout@v4
28
+ - uses: actions/setup-python@v5
29
+ with:
30
+ python-version: ${{ matrix.python-version }}
31
+ - name: Build wheels
32
+ uses: PyO3/maturin-action@v1
33
+ with:
34
+ target: ${{ matrix.target }}
35
+ args: -i python${{ matrix.python-version }} --release --sdist --out dist
36
+ sccache: 'true'
37
+ manylinux: auto
38
+ - name: Upload wheels
39
+ uses: actions/upload-artifact@v4
40
+ with:
41
+ name: wheels-linux-${{ matrix.target }}-${{ matrix.python-version }}
42
+ path: dist
43
+
44
+ musllinux:
45
+ runs-on: ubuntu-latest
46
+ strategy:
47
+ matrix:
48
+ target: [x86_64, x86, aarch64, armv7]
49
+ python-version: ["3.10", "3.11", "3.12"]
50
+ steps:
51
+ - uses: actions/checkout@v4
52
+ - uses: actions/setup-python@v5
53
+ with:
54
+ python-version: ${{ matrix.python-version }}
55
+ - name: Build wheels
56
+ uses: PyO3/maturin-action@v1
57
+ with:
58
+ target: ${{ matrix.target }}
59
+ args: -i python${{ matrix.python-version }} --release --sdist --out dist
60
+ sccache: 'true'
61
+ manylinux: musllinux_1_2
62
+ - name: Upload wheels
63
+ uses: actions/upload-artifact@v4
64
+ with:
65
+ name: wheels-musllinux-${{ matrix.target }}-${{ matrix.python-version }}
66
+ path: dist
67
+ windows:
68
+ runs-on: windows-latest
69
+ strategy:
70
+ matrix:
71
+ target: [x64, x86]
72
+ python-version: ["3.10", "3.11", "3.12"]
73
+ steps:
74
+ - uses: actions/checkout@v4
75
+ - uses: actions/setup-python@v5
76
+ with:
77
+ python-version: ${{ matrix.python-version }}
78
+ architecture: ${{ matrix.target }}
79
+ - name: Build wheels
80
+ uses: PyO3/maturin-action@v1
81
+ with:
82
+ target: ${{ matrix.target }}
83
+ args: -i python --release --sdist --out dist
84
+ sccache: 'true'
85
+ - name: Upload wheels
86
+ uses: actions/upload-artifact@v4
87
+ with:
88
+ name: wheels-windows-${{ matrix.target }}-${{ matrix.python-version }}
89
+ path: dist
90
+
91
+ macos:
92
+ runs-on: ${{ matrix.platform.runner }}
93
+ strategy:
94
+ matrix:
95
+ platform:
96
+ - runner: macos-13
97
+ target: x86_64
98
+ - runner: macos-14
99
+ target: aarch64
100
+ python-version: ["3.10", "3.11", "3.12"]
101
+ steps:
102
+ - uses: actions/checkout@v4
103
+ - uses: actions/setup-python@v5
104
+ with:
105
+ python-version: ${{ matrix.python-version }}
106
+ - name: Build wheels
107
+ uses: PyO3/maturin-action@v1
108
+ with:
109
+ target: ${{ matrix.platform.target }}
110
+ args: --release --out dist -i python --sdist
111
+ sccache: 'true'
112
+ - name: Upload wheels
113
+ uses: actions/upload-artifact@v4
114
+ with:
115
+ name: wheels-macos-${{ matrix.platform.target }}-${{ matrix.python-version }}
116
+ path: dist
117
+
118
+ release:
119
+ name: Release
120
+ runs-on: ubuntu-latest
121
+ if: ${{ startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch' }}
122
+ needs: [musllinux, linux, windows, macos]
123
+ permissions:
124
+ # Use to sign the release artifacts
125
+ id-token: write
126
+ # Used to upload release artifacts
127
+ contents: write
128
+ # Used to generate artifact attestation
129
+ attestations: write
130
+ steps:
131
+ - uses: actions/download-artifact@v4
132
+ - name: Generate artifact attestation
133
+ uses: actions/attest-build-provenance@v1
134
+ with:
135
+ subject-path: 'wheels-*/*'
136
+ - name: Publish to PyPI
137
+ if: ${{ startsWith(github.ref, 'refs/tags/') }}
138
+ uses: PyO3/maturin-action@v1
139
+ env:
140
+ MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_PASSWORD }}
141
+ with:
142
+ command: upload
143
+ args: --non-interactive --skip-existing wheels-*/*
@@ -21,6 +21,7 @@ jobs:
21
21
  run: |
22
22
  python -m pip install --upgrade pip
23
23
  pip install poetry poetry-plugin-export
24
+ poetry lock
24
25
  poetry export --without-hashes --format=requirements.txt > requirements.txt
25
26
 
26
27
  - name: Install dependencies
@@ -985,7 +985,7 @@ dependencies = [
985
985
 
986
986
  [[package]]
987
987
  name = "hypern"
988
- version = "0.3.10"
988
+ version = "0.3.12"
989
989
  dependencies = [
990
990
  "bytes",
991
991
  "chrono",
@@ -1,12 +1,12 @@
1
1
  [package]
2
2
  name = "hypern"
3
- version = "0.3.10"
3
+ version = "0.3.12"
4
4
  edition = "2021"
5
5
 
6
6
  # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7
7
  [lib]
8
8
  name = "hypern"
9
- crate-type = ["cdylib"]
9
+ crate-type = ["cdylib", "staticlib"]
10
10
 
11
11
  [dependencies]
12
12
  pyo3 = { version = "0.20.0", features = ["extension-module"] }
@@ -47,3 +47,4 @@ strip = true
47
47
 
48
48
  [package.metadata.maturin]
49
49
  name = "hypern"
50
+ python-source = "hypern"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hypern
3
- Version: 0.3.10
3
+ Version: 0.3.12
4
4
  Classifier: Programming Language :: Rust
5
5
  Classifier: Programming Language :: Python :: Implementation :: CPython
6
6
  Classifier: Programming Language :: Python :: Implementation :: PyPy
@@ -5,6 +5,9 @@ from hypern.ws import WebsocketRoute, WebSocketSession
5
5
  from .application import Hypern
6
6
  from .hypern import Request, Response
7
7
  from .response import FileResponse, HTMLResponse, JSONResponse, PlainTextResponse, RedirectResponse
8
+ from .hypern import BackgroundTask
9
+ from .hypern import BackgroundTasks
10
+ from .hypern import Scheduler
8
11
 
9
12
  __all__ = [
10
13
  "Hypern",
@@ -21,4 +24,7 @@ __all__ = [
21
24
  "PlainTextResponse",
22
25
  "RedirectResponse",
23
26
  "logger",
27
+ "BackgroundTask",
28
+ "BackgroundTasks",
29
+ "Scheduler",
24
30
  ]
@@ -10,8 +10,9 @@ import psutil
10
10
  from typing_extensions import Annotated, Doc
11
11
 
12
12
  from hypern.args_parser import ArgsConfig
13
- from hypern.datastructures import Contact, HTTPMethod, Info, License
14
- from hypern.hypern import DatabaseConfig, FunctionInfo, MiddlewareConfig, Router, Server, WebsocketRouter
13
+ from hypern.datastructures import Contact, Info, License
14
+ from hypern.enum import HTTPMethod
15
+ from hypern.hypern import DatabaseConfig, FunctionInfo, MiddlewareConfig, Router, Server, WebsocketRouter, Scheduler
15
16
  from hypern.hypern import Route as InternalRoute
16
17
  from hypern.logging import logger
17
18
  from hypern.middleware import Middleware
@@ -19,7 +20,6 @@ from hypern.openapi import SchemaGenerator, SwaggerUI
19
20
  from hypern.processpool import run_processes
20
21
  from hypern.response import HTMLResponse, JSONResponse
21
22
  from hypern.routing import Route
22
- from hypern.scheduler import Scheduler
23
23
  from hypern.ws import WebsocketRoute
24
24
 
25
25
  AppType = TypeVar("AppType", bound="Hypern")
@@ -243,7 +243,7 @@ class Hypern:
243
243
  self.thread_config = ThreadConfigurator().get_config()
244
244
 
245
245
  for route in routes or []:
246
- self.router.extend_route(route(app=self).routes)
246
+ self.router.extend_route(route())
247
247
 
248
248
  for websocket_route in websockets or []:
249
249
  for route in websocket_route.routes:
@@ -438,6 +438,7 @@ class Hypern:
438
438
  self.args.max_blocking_threads = self.thread_config.max_blocking_threads
439
439
 
440
440
  if self.args.http2:
441
+ logger.info("HTTP/2 enabled")
441
442
  server.enable_http2()
442
443
 
443
444
  run_processes(
@@ -1,5 +1,4 @@
1
1
  from typing import Optional
2
- from enum import Enum
3
2
  from pydantic import BaseModel, AnyUrl
4
3
 
5
4
 
@@ -26,15 +25,3 @@ class Info(BaseModelWithConfig):
26
25
  contact: Optional[Contact] = None
27
26
  license: Optional[License] = None
28
27
  version: str
29
-
30
-
31
- class HTTPMethod(Enum):
32
- GET = "GET"
33
- POST = "POST"
34
- PUT = "PUT"
35
- DELETE = "DELETE"
36
- PATCH = "PATCH"
37
- OPTIONS = "OPTIONS"
38
- HEAD = "HEAD"
39
- TRACE = "TRACE"
40
- CONNECT = "CONNECT"
@@ -11,3 +11,15 @@ class ErrorCode(Enum):
11
11
  METHOD_NOT_ALLOW = "METHOD_NOT_ALLOW"
12
12
  UNAUTHORIZED = "UNAUTHORIZED"
13
13
  VALIDATION_ERROR = "VALIDATION_ERROR"
14
+
15
+
16
+ class HTTPMethod(Enum):
17
+ GET = "GET"
18
+ POST = "POST"
19
+ PUT = "PUT"
20
+ DELETE = "DELETE"
21
+ PATCH = "PATCH"
22
+ OPTIONS = "OPTIONS"
23
+ HEAD = "HEAD"
24
+ TRACE = "TRACE"
25
+ CONNECT = "CONNECT"
@@ -120,8 +120,6 @@ def initialize_event_loop(max_blocking_threads: int = 100) -> asyncio.AbstractEv
120
120
  loop = uvloop.new_event_loop()
121
121
  asyncio.set_event_loop(loop)
122
122
 
123
- loop.slow_callback_duration = 0.1 # Log warnings for slow callbacks
124
- loop.set_debug(False) # Disable debug mode
125
123
  return loop
126
124
 
127
125
 
@@ -2,12 +2,10 @@ from __future__ import annotations
2
2
 
3
3
  import typing
4
4
  from urllib.parse import quote
5
- from hypern.hypern import Response as InternalResponse, Header
5
+ from hypern.hypern import Response as InternalResponse, Header, BackgroundTask, BackgroundTasks
6
6
  import orjson
7
7
  import msgpack
8
8
 
9
- from hypern.background import BackgroundTask, BackgroundTasks
10
-
11
9
 
12
10
  class BaseResponse:
13
11
  media_type = None
@@ -36,7 +36,7 @@ class ParamParser:
36
36
  return {k: v[0] for k, v in query_params.items()}
37
37
 
38
38
  def _parse_path_params(self) -> dict:
39
- return lambda: dict(self.request.path_params.items())
39
+ return dict(self.request.path_params.items())
40
40
 
41
41
  def _parse_form_data(self) -> dict:
42
42
  return self.request.json()
@@ -9,8 +9,10 @@ from pydantic import BaseModel
9
9
  from pydantic.fields import FieldInfo
10
10
 
11
11
  from hypern.auth.authorization import Authorization
12
- from hypern.datastructures import HTTPMethod
13
- from hypern.hypern import FunctionInfo, Request, Router
12
+
13
+ from hypern.enum import HTTPMethod
14
+ from hypern.hypern import FunctionInfo, Request
15
+
14
16
  from hypern.hypern import Route as InternalRoute
15
17
 
16
18
  from .dispatcher import dispatch
@@ -20,6 +22,16 @@ def get_field_type(field):
20
22
  return field.outer_type_
21
23
 
22
24
 
25
+ def join_url_paths(*parts):
26
+ first = parts[0]
27
+ parts = [part.strip("/") for part in parts]
28
+ starts_with_slash = first.startswith("/") if first else False
29
+ joined = "/".join(part for part in parts if part)
30
+ if starts_with_slash:
31
+ joined = "/" + joined
32
+ return joined
33
+
34
+
23
35
  def pydantic_to_swagger(model: type[BaseModel] | dict):
24
36
  if isinstance(model, dict):
25
37
  # Handle the case when a dict is passed instead of a Pydantic model
@@ -216,18 +228,16 @@ class Route:
216
228
  func_info = FunctionInfo(handler=handler, is_async=is_async)
217
229
  return InternalRoute(path=path, function=func_info, method=method)
218
230
 
219
- def __call__(self, app, *args: Any, **kwds: Any) -> Any:
220
- router = Router(self.path)
231
+ def __call__(self, *args: Any, **kwds: Any) -> Any:
232
+ routes = []
221
233
 
222
234
  # Validate handlers
223
235
  if not self.endpoint and not self.functional_handlers:
224
236
  raise ValueError(f"No handler found for route: {self.path}")
225
237
 
226
238
  # Handle functional routes
227
- for route in self.functional_handlers:
228
- router.add_route(route=route)
229
239
  if not self.endpoint:
230
- return router
240
+ return self.functional_handlers
231
241
 
232
242
  # Handle class-based routes
233
243
  for name, func in self.endpoint.__dict__.items():
@@ -235,15 +245,15 @@ class Route:
235
245
  sig = inspect.signature(func)
236
246
  doc = self.swagger_generate(sig, func.__doc__)
237
247
  endpoint_obj = self.endpoint()
238
- route = self.make_internal_route(path="/", handler=endpoint_obj.dispatch, method=name.upper())
248
+ route = self.make_internal_route(path=self.path, handler=endpoint_obj.dispatch, method=name.upper())
239
249
  route.doc = doc
240
- router.add_route(route=route)
250
+ routes.append(route)
241
251
  del endpoint_obj # free up memory
242
- return router
252
+ return routes
243
253
 
244
254
  def add_route(
245
255
  self,
246
- path: str,
256
+ func_path: str,
247
257
  method: str,
248
258
  ) -> Callable:
249
259
  def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
@@ -251,7 +261,7 @@ class Route:
251
261
  return await dispatch(func, request, inject)
252
262
 
253
263
  sig = inspect.signature(func)
254
- route = self.make_internal_route(path=path, handler=functional_wrapper, method=method.upper())
264
+ route = self.make_internal_route(path=join_url_paths(self.path, func_path), handler=functional_wrapper, method=method.upper())
255
265
  route.doc = self.swagger_generate(sig, func.__doc__)
256
266
 
257
267
  self.functional_handlers.append(route)
@@ -5,7 +5,7 @@ build-backend = "maturin"
5
5
 
6
6
  [project]
7
7
  name = "hypern"
8
- version = "0.3.10"
8
+ version = "0.3.12"
9
9
  description = "A Fast Async Python backend with a Rust runtime."
10
10
  authors = [{ name = "Martin Dang", email = "vannghiem848@gmail.com" }]
11
11
  requires-python = ">=3.10"
@@ -37,7 +37,7 @@ module-name = "hypern"
37
37
 
38
38
  [tool.poetry]
39
39
  name = "hypern"
40
- version = "0.3.10"
40
+ version = "0.3.12"
41
41
  description = "A Fast Async Python backend with a Rust runtime."
42
42
  authors = ["Martin Dang <vannghiem848@gmail.com>"]
43
43
 
@@ -11,7 +11,6 @@ mod ws;
11
11
  mod executor;
12
12
  mod middlewares;
13
13
  mod database;
14
- mod mem_pool;
15
14
  mod di;
16
15
 
17
16
  #[pymodule]
@@ -28,7 +27,6 @@ fn hypern(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
28
27
  m.add_class::<server::Server>()?;
29
28
  m.add_class::<router::route::Route>()?;
30
29
  m.add_class::<router::router::Router>()?;
31
- m.add_class::<types::http::HttpMethod>()?;
32
30
  m.add_class::<types::function_info::FunctionInfo>()?;
33
31
  m.add_class::<types::response::PyResponse>()?;
34
32
  m.add_class::<types::header::Header>()?;
@@ -0,0 +1,3 @@
1
+ pub mod route;
2
+ pub mod router;
3
+ pub mod radix;
@@ -0,0 +1,160 @@
1
+ use crate::router::route::Route;
2
+ use pyo3::prelude::*;
3
+ use std::collections::HashMap;
4
+
5
+ #[derive(Debug)]
6
+ #[pyclass]
7
+ #[derive(Clone)]
8
+ pub struct RadixNode {
9
+ pub path: String,
10
+ pub children: HashMap<char, RadixNode>,
11
+ pub is_endpoint: bool,
12
+ pub routes: HashMap<String, Route>,
13
+ pub param_name: Option<String>,
14
+ }
15
+
16
+ impl Default for RadixNode {
17
+ fn default() -> Self {
18
+ Self::new()
19
+ }
20
+ }
21
+ impl RadixNode {
22
+ pub fn new() -> Self {
23
+ Self {
24
+ path: String::new(),
25
+ children: HashMap::new(),
26
+ is_endpoint: false,
27
+ routes: HashMap::new(),
28
+ param_name: None,
29
+ }
30
+ }
31
+
32
+ pub fn insert(&mut self, path: &str, route: Route) {
33
+ // Normalize the path first
34
+ let normalized_path = if path == "/" {
35
+ String::new()
36
+ } else {
37
+ path.trim_end_matches('/').to_string()
38
+ };
39
+
40
+ // For root path or empty path after normalization
41
+ if normalized_path.is_empty() {
42
+ self.is_endpoint = true;
43
+ self.routes.insert(route.method.to_uppercase(), route);
44
+ return;
45
+ }
46
+
47
+ let segments: Vec<&str> = normalized_path.split('/')
48
+ .filter(|s| !s.is_empty())
49
+ .collect();
50
+
51
+ self._insert_segments(&segments, 0, route);
52
+ }
53
+
54
+ fn _insert_segments(&mut self, segments: &[&str], index: usize, route: Route) {
55
+ if index >= segments.len() {
56
+ self.is_endpoint = true;
57
+ self.routes.insert(route.method.to_uppercase(), route);
58
+ return;
59
+ }
60
+
61
+ let segment = segments[index];
62
+
63
+ // For parameter segments
64
+ if segment.starts_with(':') {
65
+ let param_name = segment[1..].to_string();
66
+ let param_node = self.children
67
+ .entry(':')
68
+ .or_insert_with(|| {
69
+ let mut node = RadixNode::new();
70
+ node.param_name = Some(param_name.clone());
71
+ node
72
+ });
73
+ param_node._insert_segments(segments, index + 1, route);
74
+ return;
75
+ }
76
+
77
+ // For static segments
78
+ let first_char = segment.chars().next().unwrap();
79
+ let node = self.children
80
+ .entry(first_char)
81
+ .or_insert_with(|| {
82
+ let mut node = RadixNode::new();
83
+ node.path = segment.to_string();
84
+ node
85
+ });
86
+
87
+ if node.path == segment {
88
+ node._insert_segments(segments, index + 1, route);
89
+ } else {
90
+ // Create new node for different path
91
+ let mut new_node = RadixNode::new();
92
+ new_node.path = segment.to_string();
93
+ new_node._insert_segments(segments, index + 1, route);
94
+ self.children.insert(first_char, new_node);
95
+ }
96
+ }
97
+
98
+ pub fn find(&self, path: &str, method: &str) -> Option<(&Route, HashMap<String, String>)> {
99
+ let normalized_path = if path == "/" {
100
+ String::new()
101
+ } else {
102
+ path.trim_end_matches('/').to_string()
103
+ };
104
+
105
+ let mut params = HashMap::new();
106
+ if normalized_path.is_empty() {
107
+ return if self.is_endpoint {
108
+ self.routes.get(&method.to_uppercase()).map(|r| (r, params))
109
+ } else {
110
+ None
111
+ };
112
+ }
113
+
114
+ let segments: Vec<&str> = normalized_path.split('/')
115
+ .filter(|s| !s.is_empty())
116
+ .collect();
117
+
118
+ self._find_segments(&segments, 0, method, &mut params)
119
+ }
120
+
121
+ fn _find_segments<'a>(
122
+ &'a self,
123
+ segments: &[&str],
124
+ index: usize,
125
+ method: &str,
126
+ params: &mut HashMap<String, String>,
127
+ ) -> Option<(&'a Route, HashMap<String, String>)> {
128
+ if index >= segments.len() {
129
+ return if self.is_endpoint {
130
+ self.routes.get(&method.to_uppercase()).map(|r| (r, params.clone()))
131
+ } else {
132
+ None
133
+ };
134
+ }
135
+
136
+ let segment = segments[index];
137
+
138
+ // Try exact static match first
139
+ for (_, child) in self.children.iter() {
140
+ if child.path == segment {
141
+ if let Some(result) = child._find_segments(segments, index + 1, method, params) {
142
+ return Some(result);
143
+ }
144
+ }
145
+ }
146
+
147
+ // Try parameter match
148
+ if let Some(param_node) = self.children.get(&':') {
149
+ if let Some(param_name) = &param_node.param_name {
150
+ params.insert(param_name.clone(), segment.to_string());
151
+ if let Some(result) = param_node._find_segments(segments, index + 1, method, params) {
152
+ return Some(result);
153
+ }
154
+ params.remove(param_name);
155
+ }
156
+ }
157
+
158
+ None
159
+ }
160
+ }
@@ -41,11 +41,6 @@ impl Route {
41
41
  self.path, self.method))
42
42
  }
43
43
 
44
- // Check if route matches given path and method
45
- pub fn matches(&self, path: &str, method: &str) -> bool {
46
- self.path == path && self.method.to_uppercase() == method.to_uppercase()
47
- }
48
-
49
44
  // Update the route path
50
45
  pub fn update_path(&mut self, new_path: &str) {
51
46
  self.path = new_path.to_string();
@@ -109,4 +104,29 @@ impl Route {
109
104
  _ => 99
110
105
  }
111
106
  }
107
+
108
+ pub fn matches(&self, path: &str, method: &str) -> bool {
109
+ if self.method.to_uppercase() != method.to_uppercase() {
110
+ return false;
111
+ }
112
+
113
+ let route_parts: Vec<&str> = self.path.split('/')
114
+ .filter(|s| !s.is_empty())
115
+ .collect();
116
+ let path_parts: Vec<&str> = path.split('/')
117
+ .filter(|s| !s.is_empty())
118
+ .collect();
119
+
120
+ if route_parts.len() != path_parts.len() {
121
+ return false;
122
+ }
123
+
124
+ for (route_part, path_part) in route_parts.iter().zip(path_parts.iter()) {
125
+ if !route_part.starts_with(':') && route_part != path_part {
126
+ return false;
127
+ }
128
+ }
129
+
130
+ true
131
+ }
112
132
  }