harp-proxy 0.5.0b3__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.
Files changed (421) hide show
  1. harp/__init__.py +101 -0
  2. harp/__main__.py +4 -0
  3. harp/_logging.py +87 -0
  4. harp/asgi/__init__.py +7 -0
  5. harp/asgi/bridge/__init__.py +0 -0
  6. harp/asgi/bridge/requests.py +71 -0
  7. harp/asgi/bridge/responses.py +42 -0
  8. harp/asgi/events.py +91 -0
  9. harp/asgi/kernel.py +164 -0
  10. harp/asgi/tests/__init__.py +0 -0
  11. harp/asgi/tests/test_http_bridge_asgi_request.py +200 -0
  12. harp/commandline/__init__.py +63 -0
  13. harp/commandline/install_dev.py +12 -0
  14. harp/commandline/start.py +112 -0
  15. harp/commandline/utils/__init__.py +0 -0
  16. harp/commandline/utils/_hacks.py +41 -0
  17. harp/commandline/utils/manager.py +125 -0
  18. harp/config/__init__.py +22 -0
  19. harp/config/adapters/__init__.py +0 -0
  20. harp/config/adapters/hypercorn.py +35 -0
  21. harp/config/application.py +76 -0
  22. harp/config/config.py +323 -0
  23. harp/config/events.py +60 -0
  24. harp/config/factories/__init__.py +0 -0
  25. harp/config/factories/kernel_factory.py +116 -0
  26. harp/config/factories/tests/__init__.py +0 -0
  27. harp/config/factories/tests/test_configuration.py +60 -0
  28. harp/config/factories/tests/test_settings.py +36 -0
  29. harp/config/settings/__init__.py +10 -0
  30. harp/config/settings/base.py +9 -0
  31. harp/config/settings/disabled.py +10 -0
  32. harp/config/settings/from_file.py +21 -0
  33. harp/controllers/__init__.py +62 -0
  34. harp/controllers/default.py +24 -0
  35. harp/controllers/resolvers.py +37 -0
  36. harp/controllers/routing.py +128 -0
  37. harp/controllers/typing.py +7 -0
  38. harp/errors.py +29 -0
  39. harp/event_dispatcher.py +36 -0
  40. harp/http/__init__.py +21 -0
  41. harp/http/errors.py +28 -0
  42. harp/http/requests.py +115 -0
  43. harp/http/responses.py +46 -0
  44. harp/http/serializers.py +103 -0
  45. harp/http/tests/__init__.py +0 -0
  46. harp/http/tests/stubs.py +65 -0
  47. harp/http/tests/test_requests.py +189 -0
  48. harp/http/tests/test_requests_wrapped.py +83 -0
  49. harp/http/tests/test_utils_cookies.py +68 -0
  50. harp/http/typing/__init__.py +11 -0
  51. harp/http/typing/bridges.py +34 -0
  52. harp/http/typing/messages.py +33 -0
  53. harp/http/typing/serializers.py +15 -0
  54. harp/http/utils/__init__.py +7 -0
  55. harp/http/utils/cookies.py +20 -0
  56. harp/http/utils/methods.py +118 -0
  57. harp/meta/__init__.py +33 -0
  58. harp/models/__init__.py +23 -0
  59. harp/models/base.py +25 -0
  60. harp/models/blobs.py +55 -0
  61. harp/models/messages.py +15 -0
  62. harp/models/transactions.py +41 -0
  63. harp/settings.py +16 -0
  64. harp/tests/__init__.py +0 -0
  65. harp/tests/test_asgi_kernel.py +51 -0
  66. harp/tests/test_settings.py +32 -0
  67. harp/typing/__init__.py +21 -0
  68. harp/typing/global_settings.py +6 -0
  69. harp/typing/signs.py +11 -0
  70. harp/typing/storage.py +64 -0
  71. harp/utils/__init__.py +7 -0
  72. harp/utils/apdex.py +13 -0
  73. harp/utils/arguments.py +14 -0
  74. harp/utils/background.py +36 -0
  75. harp/utils/bytes.py +10 -0
  76. harp/utils/collections.py +62 -0
  77. harp/utils/dates.py +25 -0
  78. harp/utils/guids.py +4 -0
  79. harp/utils/identifiers.py +2 -0
  80. harp/utils/json.py +9 -0
  81. harp/utils/network.py +44 -0
  82. harp/utils/processes.py +14 -0
  83. harp/utils/testing/__init__.py +0 -0
  84. harp/utils/testing/applications.py +32 -0
  85. harp/utils/testing/benchmarking.py +95 -0
  86. harp/utils/testing/communicators/__init__.py +10 -0
  87. harp/utils/testing/communicators/asgi.py +64 -0
  88. harp/utils/testing/communicators/http.py +57 -0
  89. harp/utils/testing/http.py +101 -0
  90. harp/utils/testing/mixins/__init__.py +6 -0
  91. harp/utils/testing/mixins/controllers.py +68 -0
  92. harp/utils/testing/stub_api/__init__.py +33 -0
  93. harp/utils/tests/__init__.py +0 -0
  94. harp/utils/tests/test_collections.py +84 -0
  95. harp/utils/tests/test_dates.py +17 -0
  96. harp/utils/tests/test_urls.py +6 -0
  97. harp/utils/urls.py +5 -0
  98. harp/views/__init__.py +17 -0
  99. harp/views/json.py +62 -0
  100. harp/views/strings.py +14 -0
  101. harp_apps/__init__.py +1 -0
  102. harp_apps/contrib/__init__.py +1 -0
  103. harp_apps/contrib/sentry/__app__.py +27 -0
  104. harp_apps/contrib/sentry/__init__.py +1 -0
  105. harp_apps/contrib/sentry/settings.py +6 -0
  106. harp_apps/dashboard/__app__.py +47 -0
  107. harp_apps/dashboard/__init__.py +1 -0
  108. harp_apps/dashboard/controllers/__init__.py +144 -0
  109. harp_apps/dashboard/controllers/blobs.py +25 -0
  110. harp_apps/dashboard/controllers/overview.py +131 -0
  111. harp_apps/dashboard/controllers/system.py +78 -0
  112. harp_apps/dashboard/controllers/transactions.py +104 -0
  113. harp_apps/dashboard/docs/development/index.rst +24 -0
  114. harp_apps/dashboard/docs/development/tests_e2e.rst +152 -0
  115. harp_apps/dashboard/docs/development/tests_unit.rst +209 -0
  116. harp_apps/dashboard/docs/examples/auth.basic.yml +5 -0
  117. harp_apps/dashboard/docs/examples/auth.yml +5 -0
  118. harp_apps/dashboard/docs/examples/devserver.yml +3 -0
  119. harp_apps/dashboard/docs/examples/main.yml +6 -0
  120. harp_apps/dashboard/docs/index.rst +96 -0
  121. harp_apps/dashboard/filters/__init__.py +13 -0
  122. harp_apps/dashboard/filters/base.py +31 -0
  123. harp_apps/dashboard/filters/transaction_endpoint.py +21 -0
  124. harp_apps/dashboard/filters/transaction_flag.py +6 -0
  125. harp_apps/dashboard/filters/transaction_method.py +6 -0
  126. harp_apps/dashboard/filters/transaction_status.py +6 -0
  127. harp_apps/dashboard/filters/utils.py +9 -0
  128. harp_apps/dashboard/frontend/.eslintrc.cjs +46 -0
  129. harp_apps/dashboard/frontend/.gitignore +29 -0
  130. harp_apps/dashboard/frontend/.ladle/components.tsx +13 -0
  131. harp_apps/dashboard/frontend/.ladle/index.css +3 -0
  132. harp_apps/dashboard/frontend/.prettierrc +10 -0
  133. harp_apps/dashboard/frontend/index.html +14 -0
  134. harp_apps/dashboard/frontend/mockServiceWorker.js +287 -0
  135. harp_apps/dashboard/frontend/package.json +107 -0
  136. harp_apps/dashboard/frontend/playwright.config.ts +77 -0
  137. harp_apps/dashboard/frontend/pnpm-lock.yaml +7713 -0
  138. harp_apps/dashboard/frontend/postcss.config.js +6 -0
  139. harp_apps/dashboard/frontend/public/harp.svg +11 -0
  140. harp_apps/dashboard/frontend/src/Assets/logo.svg +18 -0
  141. harp_apps/dashboard/frontend/src/Components/Badges/ApdexBadge.tsx +30 -0
  142. harp_apps/dashboard/frontend/src/Components/Badges/RequestMethodBadge.tsx +19 -0
  143. harp_apps/dashboard/frontend/src/Components/Badges/ResponseStatusBadge.tsx +43 -0
  144. harp_apps/dashboard/frontend/src/Components/Badges/StyledJumboBadge.tsx +23 -0
  145. harp_apps/dashboard/frontend/src/Components/Badges/constants.ts +11 -0
  146. harp_apps/dashboard/frontend/src/Components/Badges/index.ts +0 -0
  147. harp_apps/dashboard/frontend/src/Components/Layout/Layout.test.tsx +17 -0
  148. harp_apps/dashboard/frontend/src/Components/Layout/Layout.tsx +58 -0
  149. harp_apps/dashboard/frontend/src/Components/Layout/__snapshots__/Layout.test.tsx.snap +152 -0
  150. harp_apps/dashboard/frontend/src/Components/Layout/index.ts +1 -0
  151. harp_apps/dashboard/frontend/src/Components/Page/Page.test.tsx +41 -0
  152. harp_apps/dashboard/frontend/src/Components/Page/Page.tsx +64 -0
  153. harp_apps/dashboard/frontend/src/Components/Page/PageTitle.tsx +25 -0
  154. harp_apps/dashboard/frontend/src/Components/Page/__snapshots__/Page.test.tsx.snap +39 -0
  155. harp_apps/dashboard/frontend/src/Components/Page/index.tsx +3 -0
  156. harp_apps/dashboard/frontend/src/Components/Utilities/OnQuerySuccess.tsx +47 -0
  157. harp_apps/dashboard/frontend/src/Domain/Api/Types/ItemList.ts +3 -0
  158. harp_apps/dashboard/frontend/src/Domain/Api/Types/index.ts +1 -0
  159. harp_apps/dashboard/frontend/src/Domain/Api/index.ts +1 -0
  160. harp_apps/dashboard/frontend/src/Domain/Api/useApi.ts +63 -0
  161. harp_apps/dashboard/frontend/src/Domain/Overview/index.ts +1 -0
  162. harp_apps/dashboard/frontend/src/Domain/Overview/useOverviewDataQuery.ts +27 -0
  163. harp_apps/dashboard/frontend/src/Domain/Overview/useSummaryDataQuery.tsx +14 -0
  164. harp_apps/dashboard/frontend/src/Domain/System/index.ts +4 -0
  165. harp_apps/dashboard/frontend/src/Domain/System/useSystemDependenciesQuery.ts +10 -0
  166. harp_apps/dashboard/frontend/src/Domain/System/useSystemQuery.ts +12 -0
  167. harp_apps/dashboard/frontend/src/Domain/System/useSystemSettingsQuery.ts +14 -0
  168. harp_apps/dashboard/frontend/src/Domain/System/useSystemStorageQuery.ts +8 -0
  169. harp_apps/dashboard/frontend/src/Domain/Transactions/Utils/getRequestFromTransactionMessages.tsx +11 -0
  170. harp_apps/dashboard/frontend/src/Domain/Transactions/Utils/getResponseFromTransactionMessages.tsx +13 -0
  171. harp_apps/dashboard/frontend/src/Domain/Transactions/Utils/index.ts +2 -0
  172. harp_apps/dashboard/frontend/src/Domain/Transactions/index.ts +4 -0
  173. harp_apps/dashboard/frontend/src/Domain/Transactions/useBlobQuery.ts +25 -0
  174. harp_apps/dashboard/frontend/src/Domain/Transactions/useSetUserFlagMutation.ts +16 -0
  175. harp_apps/dashboard/frontend/src/Domain/Transactions/useTransactionsDetailQuery.ts +14 -0
  176. harp_apps/dashboard/frontend/src/Domain/Transactions/useTransactionsFiltersQuery.ts +17 -0
  177. harp_apps/dashboard/frontend/src/Domain/Transactions/useTransactionsListQuery.ts +44 -0
  178. harp_apps/dashboard/frontend/src/Models/Overview.d.ts +14 -0
  179. harp_apps/dashboard/frontend/src/Models/Schemas/Transaction.json +76 -0
  180. harp_apps/dashboard/frontend/src/Models/Transaction.d.ts +40 -0
  181. harp_apps/dashboard/frontend/src/Pages/Overview/Components/BaseTransactionsOverview.test.tsx +18 -0
  182. harp_apps/dashboard/frontend/src/Pages/Overview/Components/BaseTransactionsOverview.tsx +36 -0
  183. harp_apps/dashboard/frontend/src/Pages/Overview/Components/Charts/TransactionsChart.test.tsx +49 -0
  184. harp_apps/dashboard/frontend/src/Pages/Overview/Components/Charts/TransactionsChart.tsx +93 -0
  185. harp_apps/dashboard/frontend/src/Pages/Overview/Components/Charts/__snapshots__/TransactionsChart.test.tsx.snap +456 -0
  186. harp_apps/dashboard/frontend/src/Pages/Overview/Components/Charts/index.ts +1 -0
  187. harp_apps/dashboard/frontend/src/Pages/Overview/Components/PerformancesSummary.tsx +17 -0
  188. harp_apps/dashboard/frontend/src/Pages/Overview/Components/RateSummary.tsx +32 -0
  189. harp_apps/dashboard/frontend/src/Pages/Overview/Components/__snapshots__/BaseTransactionsOverview.test.tsx.snap +59 -0
  190. harp_apps/dashboard/frontend/src/Pages/Overview/Components/index.ts +1 -0
  191. harp_apps/dashboard/frontend/src/Pages/Overview/Containers/TransactionsOverview.test.tsx +18 -0
  192. harp_apps/dashboard/frontend/src/Pages/Overview/Containers/TransactionsOverview.tsx +23 -0
  193. harp_apps/dashboard/frontend/src/Pages/Overview/Containers/__snapshots__/TransactionsOverview.test.tsx.snap +59 -0
  194. harp_apps/dashboard/frontend/src/Pages/Overview/Containers/index.ts +1 -0
  195. harp_apps/dashboard/frontend/src/Pages/Overview/OverviewPage.test.tsx +12 -0
  196. harp_apps/dashboard/frontend/src/Pages/Overview/OverviewPage.tsx +107 -0
  197. harp_apps/dashboard/frontend/src/Pages/Overview/__snapshots__/OverviewPage.test.tsx.snap +675 -0
  198. harp_apps/dashboard/frontend/src/Pages/Overview/index.ts +1 -0
  199. harp_apps/dashboard/frontend/src/Pages/Overview/utils.ts +1 -0
  200. harp_apps/dashboard/frontend/src/Pages/System/Components/SettingsTable.test.tsx +54 -0
  201. harp_apps/dashboard/frontend/src/Pages/System/Components/SettingsTable.tsx +68 -0
  202. harp_apps/dashboard/frontend/src/Pages/System/Components/Topology/Topology.test.tsx +29 -0
  203. harp_apps/dashboard/frontend/src/Pages/System/Components/Topology/Topology.tsx +78 -0
  204. harp_apps/dashboard/frontend/src/Pages/System/Components/Topology/index.ts +1 -0
  205. harp_apps/dashboard/frontend/src/Pages/System/Components/index.ts +1 -0
  206. harp_apps/dashboard/frontend/src/Pages/System/SystemDependenciesTabPanel.test.tsx +20 -0
  207. harp_apps/dashboard/frontend/src/Pages/System/SystemDependenciesTabPanel.tsx +23 -0
  208. harp_apps/dashboard/frontend/src/Pages/System/SystemPage.test.tsx +30 -0
  209. harp_apps/dashboard/frontend/src/Pages/System/SystemPage.tsx +25 -0
  210. harp_apps/dashboard/frontend/src/Pages/System/SystemSettingsTabPanel.test.tsx +20 -0
  211. harp_apps/dashboard/frontend/src/Pages/System/SystemSettingsTabPanel.tsx +22 -0
  212. harp_apps/dashboard/frontend/src/Pages/System/SystemStorageTabPanel.tsx +23 -0
  213. harp_apps/dashboard/frontend/src/Pages/System/SystemTopologyTabPanel.test.tsx +20 -0
  214. harp_apps/dashboard/frontend/src/Pages/System/SystemTopologyTabPanel.tsx +32 -0
  215. harp_apps/dashboard/frontend/src/Pages/System/__snapshots__/SystemDependenciesTabPanel.test.tsx.snap +74 -0
  216. harp_apps/dashboard/frontend/src/Pages/System/__snapshots__/SystemPage.test.tsx.snap +306 -0
  217. harp_apps/dashboard/frontend/src/Pages/System/__snapshots__/SystemSettingsTabPanel.test.tsx.snap +203 -0
  218. harp_apps/dashboard/frontend/src/Pages/System/__snapshots__/SystemTopologyTabPanel.test.tsx.snap +139 -0
  219. harp_apps/dashboard/frontend/src/Pages/System/index.ts +1 -0
  220. harp_apps/dashboard/frontend/src/Pages/Transactions/Components/Buttons.tsx +41 -0
  221. harp_apps/dashboard/frontend/src/Pages/Transactions/Components/Facets/Facet.test.tsx +11 -0
  222. harp_apps/dashboard/frontend/src/Pages/Transactions/Components/Facets/Facet.tsx +97 -0
  223. harp_apps/dashboard/frontend/src/Pages/Transactions/Components/Facets/FacetInnerLightButton.test.tsx +12 -0
  224. harp_apps/dashboard/frontend/src/Pages/Transactions/Components/Facets/FacetInnerLightButton.tsx +19 -0
  225. harp_apps/dashboard/frontend/src/Pages/Transactions/Components/Facets/FacetLabel.test.tsx +11 -0
  226. harp_apps/dashboard/frontend/src/Pages/Transactions/Components/Facets/FacetLabel.tsx +18 -0
  227. harp_apps/dashboard/frontend/src/Pages/Transactions/Components/Facets/__snapshots__/Facet.test.tsx.snap +42 -0
  228. harp_apps/dashboard/frontend/src/Pages/Transactions/Components/Facets/__snapshots__/FacetInnerLightButton.test.tsx.snap +11 -0
  229. harp_apps/dashboard/frontend/src/Pages/Transactions/Components/Facets/__snapshots__/FacetLabel.test.tsx.snap +9 -0
  230. harp_apps/dashboard/frontend/src/Pages/Transactions/Components/Facets/index.ts +2 -0
  231. harp_apps/dashboard/frontend/src/Pages/Transactions/Components/Foldable.tsx +36 -0
  232. harp_apps/dashboard/frontend/src/Pages/Transactions/Components/List/TransactionDataTable.test.tsx +95 -0
  233. harp_apps/dashboard/frontend/src/Pages/Transactions/Components/List/TransactionDataTable.tsx +109 -0
  234. harp_apps/dashboard/frontend/src/Pages/Transactions/Components/List/index.ts +1 -0
  235. harp_apps/dashboard/frontend/src/Pages/Transactions/Components/MessageBody.tsx +19 -0
  236. harp_apps/dashboard/frontend/src/Pages/Transactions/Components/MessageHeaders.tsx +31 -0
  237. harp_apps/dashboard/frontend/src/Pages/Transactions/Components/MessageSummary.tsx +41 -0
  238. harp_apps/dashboard/frontend/src/Pages/Transactions/Components/OptionalPaginator.tsx +25 -0
  239. harp_apps/dashboard/frontend/src/Pages/Transactions/Components/PrettyBody.tsx +57 -0
  240. harp_apps/dashboard/frontend/src/Pages/Transactions/Components/__snapshots__/HeadersTable.test.tsx.snap +26 -0
  241. harp_apps/dashboard/frontend/src/Pages/Transactions/Components/index.ts +0 -0
  242. harp_apps/dashboard/frontend/src/Pages/Transactions/Containers/FiltersSidebar.test.tsx +22 -0
  243. harp_apps/dashboard/frontend/src/Pages/Transactions/Containers/FiltersSidebar.tsx +101 -0
  244. harp_apps/dashboard/frontend/src/Pages/Transactions/Containers/__snapshots__/FiltersSidebar.test.tsx.snap +588 -0
  245. harp_apps/dashboard/frontend/src/Pages/Transactions/Containers/index.ts +1 -0
  246. harp_apps/dashboard/frontend/src/Pages/Transactions/TransactionDetailOnQuerySuccess.tsx +44 -0
  247. harp_apps/dashboard/frontend/src/Pages/Transactions/TransactionDetailPage.test.tsx +19 -0
  248. harp_apps/dashboard/frontend/src/Pages/Transactions/TransactionDetailPage.tsx +18 -0
  249. harp_apps/dashboard/frontend/src/Pages/Transactions/TransactionListOnQuerySuccess.tsx +67 -0
  250. harp_apps/dashboard/frontend/src/Pages/Transactions/TransactionListPage.test.tsx +17 -0
  251. harp_apps/dashboard/frontend/src/Pages/Transactions/TransactionListPage.tsx +57 -0
  252. harp_apps/dashboard/frontend/src/Pages/Transactions/__snapshots__/TransactionListPage.test.tsx.snap +307 -0
  253. harp_apps/dashboard/frontend/src/Pages/Transactions/__snapshots__/TransactionsDetailPage.test.tsx.snap +298 -0
  254. harp_apps/dashboard/frontend/src/Pages/Transactions/__snapshots__/TransactionsListPage.test.tsx.snap +359 -0
  255. harp_apps/dashboard/frontend/src/Pages/Transactions/index.ts +2 -0
  256. harp_apps/dashboard/frontend/src/Styles/GlobalStyles.tsx +18 -0
  257. harp_apps/dashboard/frontend/src/Types/filters.d.ts +4 -0
  258. harp_apps/dashboard/frontend/src/Utils/Strings.ts +20 -0
  259. harp_apps/dashboard/frontend/src/index.css +8 -0
  260. harp_apps/dashboard/frontend/src/main.tsx +71 -0
  261. harp_apps/dashboard/frontend/src/tests/mocks/api/index.ts +2 -0
  262. harp_apps/dashboard/frontend/src/tests/mocks/api/overview.ts +28 -0
  263. harp_apps/dashboard/frontend/src/tests/mocks/api/summary.ts +29 -0
  264. harp_apps/dashboard/frontend/src/tests/mocks/browser.ts +7 -0
  265. harp_apps/dashboard/frontend/src/tests/mocks/handlers.ts +186 -0
  266. harp_apps/dashboard/frontend/src/tests/mocks/node.ts +5 -0
  267. harp_apps/dashboard/frontend/src/tests/utils.tsx +28 -0
  268. harp_apps/dashboard/frontend/src/ui/Components/Badge/Badge.stories.tsx +33 -0
  269. harp_apps/dashboard/frontend/src/ui/Components/Badge/Badge.test.tsx +16 -0
  270. harp_apps/dashboard/frontend/src/ui/Components/Badge/Badge.tsx +20 -0
  271. harp_apps/dashboard/frontend/src/ui/Components/Badge/__snapshots__/Badge.test.tsx.snap +12 -0
  272. harp_apps/dashboard/frontend/src/ui/Components/Badge/index.ts +1 -0
  273. harp_apps/dashboard/frontend/src/ui/Components/Button/Button.stories.tsx +10 -0
  274. harp_apps/dashboard/frontend/src/ui/Components/Button/Button.test.tsx +21 -0
  275. harp_apps/dashboard/frontend/src/ui/Components/Button/Button.tsx +33 -0
  276. harp_apps/dashboard/frontend/src/ui/Components/Button/__snapshots__/Button.test.tsx.snap +11 -0
  277. harp_apps/dashboard/frontend/src/ui/Components/Button/index.ts +1 -0
  278. harp_apps/dashboard/frontend/src/ui/Components/ButtonGroup/ButtonGroup.stories.tsx +21 -0
  279. harp_apps/dashboard/frontend/src/ui/Components/ButtonGroup/ButtonGroup.test.tsx +34 -0
  280. harp_apps/dashboard/frontend/src/ui/Components/ButtonGroup/ButtonGroup.tsx +52 -0
  281. harp_apps/dashboard/frontend/src/ui/Components/ButtonGroup/__snapshots__/ButtonGroup.test.tsx.snap +28 -0
  282. harp_apps/dashboard/frontend/src/ui/Components/ButtonGroup/index.ts +1 -0
  283. harp_apps/dashboard/frontend/src/ui/Components/DataTable/DataTable.stories.tsx +19 -0
  284. harp_apps/dashboard/frontend/src/ui/Components/DataTable/DataTable.test.tsx +128 -0
  285. harp_apps/dashboard/frontend/src/ui/Components/DataTable/DataTable.tsx +117 -0
  286. harp_apps/dashboard/frontend/src/ui/Components/DataTable/__snapshots__/DataTable.test.tsx.snap +138 -0
  287. harp_apps/dashboard/frontend/src/ui/Components/DataTable/index.ts +1 -0
  288. harp_apps/dashboard/frontend/src/ui/Components/FormWidgets/Checkbox.test.tsx +62 -0
  289. harp_apps/dashboard/frontend/src/ui/Components/FormWidgets/Checkbox.tsx +65 -0
  290. harp_apps/dashboard/frontend/src/ui/Components/FormWidgets/FormWidgets.stories.tsx +62 -0
  291. harp_apps/dashboard/frontend/src/ui/Components/FormWidgets/Radio.test.tsx +43 -0
  292. harp_apps/dashboard/frontend/src/ui/Components/FormWidgets/Radio.tsx +28 -0
  293. harp_apps/dashboard/frontend/src/ui/Components/FormWidgets/__snapshots__/Checkbox.test.tsx.snap +30 -0
  294. harp_apps/dashboard/frontend/src/ui/Components/FormWidgets/__snapshots__/Radio.test.tsx.snap +22 -0
  295. harp_apps/dashboard/frontend/src/ui/Components/FormWidgets/index.ts +2 -0
  296. harp_apps/dashboard/frontend/src/ui/Components/Navbar/Assets/logo.svg +5 -0
  297. harp_apps/dashboard/frontend/src/ui/Components/Navbar/Navbar.stories.tsx +13 -0
  298. harp_apps/dashboard/frontend/src/ui/Components/Navbar/Navbar.test.tsx +42 -0
  299. harp_apps/dashboard/frontend/src/ui/Components/Navbar/Navbar.tsx +106 -0
  300. harp_apps/dashboard/frontend/src/ui/Components/Navbar/__snapshots__/Navbar.test.tsx.snap +83 -0
  301. harp_apps/dashboard/frontend/src/ui/Components/Navbar/index.ts +1 -0
  302. harp_apps/dashboard/frontend/src/ui/Components/Pagination/Pagination.stories.tsx +21 -0
  303. harp_apps/dashboard/frontend/src/ui/Components/Pagination/Paginator.test.tsx +55 -0
  304. harp_apps/dashboard/frontend/src/ui/Components/Pagination/Paginator.tsx +122 -0
  305. harp_apps/dashboard/frontend/src/ui/Components/Pagination/__snapshots__/Paginator.test.tsx.snap +148 -0
  306. harp_apps/dashboard/frontend/src/ui/Components/Pagination/index.ts +1 -0
  307. harp_apps/dashboard/frontend/src/ui/Components/Pane/Pane.stories.tsx +35 -0
  308. harp_apps/dashboard/frontend/src/ui/Components/Pane/Pane.test.tsx +16 -0
  309. harp_apps/dashboard/frontend/src/ui/Components/Pane/Pane.tsx +16 -0
  310. harp_apps/dashboard/frontend/src/ui/Components/Pane/__snapshots__/Pane.test.tsx.snap +9 -0
  311. harp_apps/dashboard/frontend/src/ui/Components/Pane/index.ts +1 -0
  312. harp_apps/dashboard/frontend/src/ui/Components/SearchBar/SearchBar.stories.tsx +9 -0
  313. harp_apps/dashboard/frontend/src/ui/Components/SearchBar/SearchBar.test.tsx +11 -0
  314. harp_apps/dashboard/frontend/src/ui/Components/SearchBar/SearchBar.tsx +55 -0
  315. harp_apps/dashboard/frontend/src/ui/Components/SearchBar/__snapshots__/SearchBar.test.tsx.snap +28 -0
  316. harp_apps/dashboard/frontend/src/ui/Components/SearchBar/index.ts +1 -0
  317. harp_apps/dashboard/frontend/src/ui/Components/Tabs/Tabs.stories.tsx +95 -0
  318. harp_apps/dashboard/frontend/src/ui/Components/Tabs/Tabs.test.tsx +49 -0
  319. harp_apps/dashboard/frontend/src/ui/Components/Tabs/Tabs.tsx +30 -0
  320. harp_apps/dashboard/frontend/src/ui/Components/Tabs/__snapshots__/Tabs.test.tsx.snap +42 -0
  321. harp_apps/dashboard/frontend/src/ui/Components/Tabs/index.ts +1 -0
  322. harp_apps/dashboard/frontend/src/ui/Components/Typography/Paragraphs.ts +3 -0
  323. harp_apps/dashboard/frontend/src/ui/Components/Typography/Section.ts +3 -0
  324. harp_apps/dashboard/frontend/src/ui/Components/Typography/Titles.ts +11 -0
  325. harp_apps/dashboard/frontend/src/ui/Components/Typography/Typography.stories.tsx +50 -0
  326. harp_apps/dashboard/frontend/src/ui/Components/Typography/index.ts +3 -0
  327. harp_apps/dashboard/frontend/src/ui/Utilities/index.ts +3 -0
  328. harp_apps/dashboard/frontend/src/ui/playwright.config.ts +19 -0
  329. harp_apps/dashboard/frontend/src/ui/tests/snapshot.spec.ts +44 -0
  330. harp_apps/dashboard/frontend/src/ui/tests/snapshot.spec.ts-snapshots/badge--default-darwin.png +0 -0
  331. harp_apps/dashboard/frontend/src/ui/tests/snapshot.spec.ts-snapshots/button--default-darwin.png +0 -0
  332. harp_apps/dashboard/frontend/src/ui/tests/snapshot.spec.ts-snapshots/button--primary-darwin.png +0 -0
  333. harp_apps/dashboard/frontend/src/ui/tests/snapshot.spec.ts-snapshots/button--secondary-darwin.png +0 -0
  334. harp_apps/dashboard/frontend/src/ui/tests/snapshot.spec.ts-snapshots/button-group--button-group-darwin.png +0 -0
  335. harp_apps/dashboard/frontend/src/ui/tests/snapshot.spec.ts-snapshots/data-table--default-darwin.png +0 -0
  336. harp_apps/dashboard/frontend/src/ui/tests/snapshot.spec.ts-snapshots/form-widgets--checkboxes-darwin.png +0 -0
  337. harp_apps/dashboard/frontend/src/ui/tests/snapshot.spec.ts-snapshots/form-widgets--checkboxes-with-style-darwin.png +0 -0
  338. harp_apps/dashboard/frontend/src/ui/tests/snapshot.spec.ts-snapshots/form-widgets--radios-darwin.png +0 -0
  339. harp_apps/dashboard/frontend/src/ui/tests/snapshot.spec.ts-snapshots/navbar--default-darwin.png +0 -0
  340. harp_apps/dashboard/frontend/src/ui/tests/snapshot.spec.ts-snapshots/pagination--paginator-darwin.png +0 -0
  341. harp_apps/dashboard/frontend/src/ui/tests/snapshot.spec.ts-snapshots/pane--default-darwin.png +0 -0
  342. harp_apps/dashboard/frontend/src/ui/tests/snapshot.spec.ts-snapshots/search-bar--default-darwin.png +0 -0
  343. harp_apps/dashboard/frontend/src/ui/tests/snapshot.spec.ts-snapshots/tabs--default-darwin.png +0 -0
  344. harp_apps/dashboard/frontend/src/ui/tests/snapshot.spec.ts-snapshots/typography--titles-darwin.png +0 -0
  345. harp_apps/dashboard/frontend/src/ui/tests/snapshot.spec.ts-snapshots/typography--titles-with-paragraphs-darwin.png +0 -0
  346. harp_apps/dashboard/frontend/src/vite-env.d.ts +1 -0
  347. harp_apps/dashboard/frontend/tailwind.config.js +56 -0
  348. harp_apps/dashboard/frontend/tests/overviewPage.spec.ts +7 -0
  349. harp_apps/dashboard/frontend/tests/systemDependenciesPage.spec.ts +34 -0
  350. harp_apps/dashboard/frontend/tests/transactionsPage.spec.ts +30 -0
  351. harp_apps/dashboard/frontend/tsconfig.json +29 -0
  352. harp_apps/dashboard/frontend/tsconfig.node.json +10 -0
  353. harp_apps/dashboard/frontend/types/twin.d.ts +20 -0
  354. harp_apps/dashboard/frontend/vite.config.ts +94 -0
  355. harp_apps/dashboard/frontend/vitest.setup.ts +25 -0
  356. harp_apps/dashboard/schemas/__init__.py +3 -0
  357. harp_apps/dashboard/schemas/transactions_grouped_by.py +11 -0
  358. harp_apps/dashboard/settings.py +105 -0
  359. harp_apps/dashboard/tests/__init__.py +0 -0
  360. harp_apps/dashboard/tests/test_controllers_blobs.py +52 -0
  361. harp_apps/dashboard/tests/test_controllers_transactions.py +58 -0
  362. harp_apps/dashboard/tests/test_settings.py +37 -0
  363. harp_apps/dashboard/tests/test_utils_dependencies.py +127 -0
  364. harp_apps/dashboard/tests/utils/__init__.py +0 -0
  365. harp_apps/dashboard/tests/utils/test_dates.py +64 -0
  366. harp_apps/dashboard/utils/__init__.py +0 -0
  367. harp_apps/dashboard/utils/dates.py +76 -0
  368. harp_apps/dashboard/utils/dependencies.py +30 -0
  369. harp_apps/http_client/__app__.py +20 -0
  370. harp_apps/http_client/__init__.py +1 -0
  371. harp_apps/janitor/__app__.py +18 -0
  372. harp_apps/janitor/__init__.py +0 -0
  373. harp_apps/janitor/settings.py +7 -0
  374. harp_apps/janitor/tests/__init__.py +0 -0
  375. harp_apps/janitor/tests/test_worker.py +80 -0
  376. harp_apps/janitor/worker.py +112 -0
  377. harp_apps/proxy/__app__.py +38 -0
  378. harp_apps/proxy/__init__.py +1 -0
  379. harp_apps/proxy/controllers.py +180 -0
  380. harp_apps/proxy/docs/examples/swapi.yml +5 -0
  381. harp_apps/proxy/docs/index.rst +28 -0
  382. harp_apps/proxy/events.py +3 -0
  383. harp_apps/proxy/settings.py +26 -0
  384. harp_apps/proxy/tests/__init__.py +0 -0
  385. harp_apps/proxy/tests/test_controllers_http_proxy.py +209 -0
  386. harp_apps/sqlalchemy_storage/__app__.py +35 -0
  387. harp_apps/sqlalchemy_storage/__init__.py +1 -0
  388. harp_apps/sqlalchemy_storage/constants.py +10 -0
  389. harp_apps/sqlalchemy_storage/docs/index.rst +46 -0
  390. harp_apps/sqlalchemy_storage/models/__init__.py +32 -0
  391. harp_apps/sqlalchemy_storage/models/base.py +74 -0
  392. harp_apps/sqlalchemy_storage/models/blobs.py +58 -0
  393. harp_apps/sqlalchemy_storage/models/flags.py +32 -0
  394. harp_apps/sqlalchemy_storage/models/messages.py +74 -0
  395. harp_apps/sqlalchemy_storage/models/metrics.py +42 -0
  396. harp_apps/sqlalchemy_storage/models/tags.py +32 -0
  397. harp_apps/sqlalchemy_storage/models/transactions.py +171 -0
  398. harp_apps/sqlalchemy_storage/models/users.py +31 -0
  399. harp_apps/sqlalchemy_storage/settings.py +25 -0
  400. harp_apps/sqlalchemy_storage/storage.py +467 -0
  401. harp_apps/sqlalchemy_storage/tests/__init__.py +0 -0
  402. harp_apps/sqlalchemy_storage/tests/test_application.py +36 -0
  403. harp_apps/sqlalchemy_storage/tests/test_models_base.py +74 -0
  404. harp_apps/sqlalchemy_storage/tests/test_storage_tags.py +21 -0
  405. harp_apps/sqlalchemy_storage/tests/test_storage_transactions.py +30 -0
  406. harp_apps/sqlalchemy_storage/tests/test_storage_usage.py +14 -0
  407. harp_apps/sqlalchemy_storage/utils/__init__.py +0 -0
  408. harp_apps/sqlalchemy_storage/utils/dates.py +90 -0
  409. harp_apps/sqlalchemy_storage/utils/testing/__init__.py +0 -0
  410. harp_apps/sqlalchemy_storage/utils/testing/mixins.py +53 -0
  411. harp_apps/telemetry/__app__.py +37 -0
  412. harp_apps/telemetry/__init__.py +1 -0
  413. harp_apps/telemetry/manager.py +85 -0
  414. harp_apps/telemetry/tests/__init__.py +0 -0
  415. harp_apps/telemetry/tests/test_application.py +5 -0
  416. harp_apps/telemetry/tests/test_manager.py +54 -0
  417. harp_proxy-0.5.0b3.dist-info/LICENSE.rst +43 -0
  418. harp_proxy-0.5.0b3.dist-info/METADATA +154 -0
  419. harp_proxy-0.5.0b3.dist-info/RECORD +421 -0
  420. harp_proxy-0.5.0b3.dist-info/WHEEL +4 -0
  421. harp_proxy-0.5.0b3.dist-info/entry_points.txt +3 -0
harp/__init__.py ADDED
@@ -0,0 +1,101 @@
1
+ """
2
+ The Core (:mod:`harp`) package is the root namespace of the Harp framework.
3
+
4
+ It mostly contains a reference to the :class:`Config` class, because it's the only object you need to start using Harp
5
+ using the python API (you don't *need* to use this API, configuration files should be enough for most use cases, but
6
+ if you want to, this is the starting point).
7
+
8
+ For convenience, the :func:`run` function is also available, which is a simple way to start the default server
9
+ implementation for your configuration object.
10
+
11
+ Example usage:
12
+
13
+ .. code-block:: python
14
+
15
+ from harp import Config, run
16
+
17
+ config = Config()
18
+ config.add_defaults()
19
+
20
+ if __name__ == "__main__":
21
+ run(config)
22
+
23
+ You can find more information about how configuration works in the :mod:`harp.config` module.
24
+
25
+ Contents
26
+ --------
27
+
28
+ """
29
+
30
+ import os
31
+ from subprocess import check_output
32
+
33
+ from packaging.version import InvalidVersion, Version
34
+
35
+ ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
36
+
37
+
38
+ def _parse_version(version: str, /, *, default=None) -> Version:
39
+ try:
40
+ return Version(version)
41
+ except InvalidVersion:
42
+ if "-" in version:
43
+ return _parse_version(version.rsplit("-", 1)[0], default=default)
44
+ return default
45
+
46
+
47
+ # last release
48
+ __title__ = "Core"
49
+ __version__ = "0.5.0b3"
50
+ __revision__ = __version__ # we can't commit the not yet known revision
51
+
52
+ # override with version.txt if available (after docker build for example)
53
+ if os.path.exists(os.path.join(ROOT_DIR, "version.txt")):
54
+ with open(os.path.join(ROOT_DIR, "version.txt")) as f:
55
+ __version__ = f.read().strip()
56
+
57
+ __parsed_version__ = _parse_version(__version__)
58
+
59
+ # override with current development version/revision if available (disabled in CI, for docs)
60
+ if not os.environ.get("CI", False) and os.path.exists(os.path.join(ROOT_DIR, ".git")):
61
+ __revision__ = check_output(["git", "rev-parse", "HEAD"], cwd=ROOT_DIR).decode("utf-8").strip()
62
+ try:
63
+ __version__ = (
64
+ check_output(["git", "describe", "--tags", "--always", "--dirty"], cwd=ROOT_DIR).decode("utf-8").strip()
65
+ )
66
+ __parsed_version__ = _parse_version(__version__, default=__parsed_version__)
67
+ except Exception:
68
+ __version__ = __revision__[:7]
69
+
70
+ from ._logging import get_logger # noqa: E402, isort: skip
71
+ from harp.config import Config # noqa: E402, isort: skip
72
+
73
+
74
+ def run(config: Config):
75
+ """
76
+ Run the default server using provided configuration.
77
+
78
+ :param config: Config
79
+ :return:
80
+ """
81
+ import asyncio
82
+ import sys
83
+
84
+ from harp.config.adapters.hypercorn import HypercornAdapter
85
+ from harp.config.factories.kernel_factory import KernelFactory
86
+
87
+ config.read_env(sys.argv[1:])
88
+ factory = KernelFactory(config)
89
+ server = HypercornAdapter(factory)
90
+ asyncio.run(server.serve())
91
+
92
+
93
+ __all__ = [
94
+ "Config",
95
+ "ROOT_DIR",
96
+ "__revision__",
97
+ "__version__",
98
+ "__parsed_version__",
99
+ "get_logger",
100
+ "run",
101
+ ]
harp/__main__.py ADDED
@@ -0,0 +1,4 @@
1
+ if __name__ == "__main__":
2
+ from .commandline import entrypoint
3
+
4
+ entrypoint()
harp/_logging.py ADDED
@@ -0,0 +1,87 @@
1
+ import logging.config
2
+ import os
3
+ from typing import Any
4
+
5
+ import structlog
6
+
7
+ processors = [
8
+ structlog.stdlib.filter_by_level,
9
+ structlog.contextvars.merge_contextvars,
10
+ structlog.stdlib.add_logger_name,
11
+ structlog.stdlib.add_log_level,
12
+ structlog.processors.TimeStamper(fmt="iso"),
13
+ structlog.dev.set_exc_info,
14
+ structlog.stdlib.PositionalArgumentsFormatter(),
15
+ structlog.processors.StackInfoRenderer(),
16
+ structlog.processors.UnicodeDecoder(),
17
+ structlog.stdlib.ProcessorFormatter.wrap_for_formatter,
18
+ ]
19
+
20
+ structlog.configure(
21
+ logger_factory=structlog.stdlib.LoggerFactory(),
22
+ wrapper_class=structlog.stdlib.BoundLogger,
23
+ processors=processors,
24
+ cache_logger_on_first_use=True,
25
+ )
26
+
27
+ LOGGING_FORMATTERS = {
28
+ "json": {
29
+ "()": structlog.stdlib.ProcessorFormatter,
30
+ "processor": structlog.processors.JSONRenderer(),
31
+ },
32
+ "plain": {
33
+ "()": structlog.stdlib.ProcessorFormatter,
34
+ "processor": structlog.dev.ConsoleRenderer(exception_formatter=structlog.dev.plain_traceback, colors=False),
35
+ },
36
+ "pretty": {
37
+ "()": structlog.stdlib.ProcessorFormatter,
38
+ "processor": structlog.dev.ConsoleRenderer(exception_formatter=structlog.dev.plain_traceback),
39
+ },
40
+ "keyvalue": {
41
+ "()": structlog.stdlib.ProcessorFormatter,
42
+ "processor": structlog.processors.KeyValueRenderer(key_order=["timestamp", "level", "event", "logger"]),
43
+ },
44
+ }
45
+
46
+ LOGGING_DEFAULT_FORMATTER = "pretty"
47
+ LOGGING_FORMATTER = os.environ.get("LOGGING_FORMATTER", LOGGING_DEFAULT_FORMATTER)
48
+ if LOGGING_FORMATTER not in LOGGING_FORMATTERS:
49
+ LOGGING_FORMATTER = LOGGING_DEFAULT_FORMATTER
50
+
51
+ logging_config = {
52
+ "version": 1,
53
+ "disable_existing_loggers": True,
54
+ "formatters": LOGGING_FORMATTERS,
55
+ "handlers": {
56
+ "console": {
57
+ "class": "logging.StreamHandler",
58
+ "formatter": LOGGING_FORMATTER,
59
+ },
60
+ },
61
+ "root": {
62
+ "handlers": ["console"],
63
+ "level": logging.INFO,
64
+ },
65
+ "loggers": {
66
+ "harp": {"level": os.environ.get("LOGGING_HARP", "INFO")},
67
+ "harp.event_dispatcher": {"level": os.environ.get("LOGGING_HARP_EVENTS", "WARNING")},
68
+ "httpcore": {"level": os.environ.get("LOGGING_HTTPCORE", "INFO")}, # todo wrap in structlog
69
+ "httpx": {"level": os.environ.get("LOGGING_HTTPX", "WARNING")}, # todo wrap in structlog
70
+ "hypercorn.access": {"level": os.environ.get("LOGGING_HYPERCORN_ACCESS", "WARNING")},
71
+ "hypercorn.error": {"level": os.environ.get("LOGGING_HYPERCORN_ERROR", "INFO")},
72
+ },
73
+ }
74
+
75
+
76
+ logging.config.dictConfig(logging_config)
77
+
78
+
79
+ def get_logger(name, *args: Any, **initial_values: Any) -> Any:
80
+ pkg, mod = name.rsplit(".", 1)
81
+ if mod in ("__init__", "__main__", "__app__"):
82
+ name = pkg
83
+
84
+ return structlog.get_logger(name, *args, **initial_values)
85
+
86
+
87
+ __all__ = ["get_logger"]
harp/asgi/__init__.py ADDED
@@ -0,0 +1,7 @@
1
+ from .kernel import ASGIKernel
2
+
3
+ __title__ = "ASGI"
4
+
5
+ __all__ = [
6
+ "ASGIKernel",
7
+ ]
File without changes
@@ -0,0 +1,71 @@
1
+ from typing import AsyncIterator, Optional, cast
2
+ from urllib.parse import parse_qsl
3
+
4
+ from asgiref.typing import ASGIReceiveCallable, HTTPScope
5
+ from multidict import CIMultiDict, MultiDict
6
+
7
+ from harp.http import HttpRequestBridge
8
+
9
+ _default_host = (None, None)
10
+
11
+
12
+ class HttpRequestAsgiBridge(HttpRequestBridge):
13
+ """Actually implements the getters required by HttpRequest using the asgi scope and receive callable. It is still
14
+ an early implementation and will need to support streaming requests in the future."""
15
+
16
+ def __init__(self, scope: HTTPScope, receive: ASGIReceiveCallable):
17
+ """
18
+ :param scope: see https://asgi.readthedocs.io/en/latest/specs/www.html#http-connection-scope
19
+ :param receive:
20
+ """
21
+ self.asgi_scope = scope
22
+ self.asgi_receive = receive
23
+
24
+ self._closed = False
25
+
26
+ def get_server_ipaddr(self) -> Optional[str]:
27
+ """Get the server IP address from asgi scope."""
28
+ try:
29
+ return (self.asgi_scope.get("server", _default_host) or _default_host)[0]
30
+ except IndexError:
31
+ return None
32
+
33
+ def get_server_port(self) -> Optional[int]:
34
+ """Get the server port from asgi scope."""
35
+ try:
36
+ return (self.asgi_scope.get("server", _default_host) or _default_host)[1]
37
+ except IndexError:
38
+ return None
39
+
40
+ def get_method(self) -> str:
41
+ """Get the HTTP method from asgi scope."""
42
+ return self.asgi_scope.get("method", None)
43
+
44
+ def get_path(self) -> str:
45
+ """Get the HTTP path from asgi scope (/foo/bar), without the query string part."""
46
+ if "raw_path" in self.asgi_scope:
47
+ return self.asgi_scope["raw_path"].decode("utf-8")
48
+ return self.asgi_scope.get("path", "/")
49
+
50
+ def get_query(self) -> MultiDict:
51
+ """Get the query string from asgi scope, as a multidict."""
52
+ return MultiDict(parse_qsl(self.asgi_scope.get("query_string", b"").decode("utf-8"), keep_blank_values=True))
53
+
54
+ def get_headers(self) -> CIMultiDict:
55
+ """Get the headers from asgi scope, as a case-insensitive multidict."""
56
+ headers = CIMultiDict()
57
+ for name, value in self.asgi_scope.get("headers", []):
58
+ headers.add(name.decode("utf-8"), value.decode("utf-8"))
59
+ return headers
60
+
61
+ async def stream(self) -> AsyncIterator[bytes]:
62
+ """Returns an async iterator that reads the request body chunks. This can only be used once, as it delegates to
63
+ the ASGI receive callable."""
64
+ if self._closed:
65
+ raise RuntimeError("Request body has already been read.")
66
+
67
+ # TODO max iter to avoid infinite loop on erroneous proto content?
68
+ while not self._closed:
69
+ message = await self.asgi_receive()
70
+ self._closed = not message.get("more_body", False)
71
+ yield cast(bytes, message.get("body", b""))
@@ -0,0 +1,42 @@
1
+ from typing import TYPE_CHECKING
2
+
3
+ from asgiref.typing import ASGISendCallable
4
+ from multidict import CIMultiDictProxy
5
+
6
+ from harp.utils.bytes import ensure_bytes
7
+
8
+ if TYPE_CHECKING:
9
+ from harp.http import HttpResponse
10
+
11
+
12
+ class HttpResponseAsgiBridge: # todo protocol HttpResponseBridge
13
+ """Implements the ability of sending our HttpResponse object over the asgi protocol. This is still early impl. and
14
+ it will need to support streaming responses in the future."""
15
+
16
+ def __init__(self, response: "HttpResponse", send: ASGISendCallable):
17
+ self.response = response
18
+ self.asgi_send = send
19
+
20
+ async def send(self):
21
+ # set the headers as read only
22
+ self.response._headers = CIMultiDictProxy(self.response._headers)
23
+
24
+ # prepare headers for asgi
25
+ headers = tuple((ensure_bytes(k), ensure_bytes(v)) for k, v in self.response.headers.items())
26
+
27
+ # start the response
28
+ await self.asgi_send(
29
+ {
30
+ "type": "http.response.start",
31
+ "status": self.response.status,
32
+ "headers": headers,
33
+ }
34
+ )
35
+
36
+ # send the body
37
+ await self.asgi_send(
38
+ {
39
+ "type": "http.response.body",
40
+ "body": ensure_bytes(self.response.body),
41
+ }
42
+ )
harp/asgi/events.py ADDED
@@ -0,0 +1,91 @@
1
+ from functools import cached_property
2
+ from typing import TYPE_CHECKING, Any, Optional
3
+
4
+ from whistle import Event
5
+
6
+ from harp.http import BaseMessage, HttpRequest
7
+ from harp.models.transactions import Transaction
8
+
9
+ if TYPE_CHECKING:
10
+ from harp.http import HttpResponse
11
+
12
+ EVENT_CORE_STARTED = "core.started"
13
+
14
+
15
+ class RequestEvent(Event):
16
+ name = "core.request"
17
+
18
+ def __init__(self, request: HttpRequest):
19
+ self._request = request
20
+ self._controller = None
21
+
22
+ @cached_property
23
+ def request(self):
24
+ return self._request
25
+
26
+ @property
27
+ def controller(self):
28
+ return self._controller
29
+
30
+ def set_controller(self, controller):
31
+ self._controller = controller
32
+
33
+
34
+ EVENT_CORE_REQUEST = RequestEvent.name
35
+
36
+
37
+ class ControllerEvent(RequestEvent):
38
+ name = "core.controller"
39
+
40
+ def __init__(self, request: HttpRequest, controller):
41
+ super().__init__(request)
42
+ self._controller = controller
43
+
44
+
45
+ EVENT_CORE_CONTROLLER = ControllerEvent.name
46
+
47
+
48
+ class ResponseEvent(RequestEvent):
49
+ name = "core.response"
50
+
51
+ def __init__(self, request: HttpRequest, response):
52
+ super().__init__(request)
53
+ self.response = response
54
+
55
+
56
+ EVENT_CORE_RESPONSE = ResponseEvent.name
57
+
58
+
59
+ class ControllerViewEvent(RequestEvent):
60
+ """
61
+ The view event allows to transform controller return values into response objects.
62
+ """
63
+
64
+ name = "controller.view"
65
+
66
+ value: Any
67
+ response: Optional["HttpResponse"]
68
+
69
+ def __init__(self, request: HttpRequest, value: Any):
70
+ super().__init__(request)
71
+
72
+ self.value = value
73
+ self.response = None
74
+
75
+ def set_response(self, response: "HttpResponse"):
76
+ self.response = response
77
+ self.stop_propagation()
78
+
79
+
80
+ EVENT_CONTROLLER_VIEW = ControllerViewEvent.name
81
+
82
+
83
+ class TransactionEvent(Event):
84
+ def __init__(self, transaction: Transaction):
85
+ self.transaction = transaction
86
+
87
+
88
+ class MessageEvent(TransactionEvent):
89
+ def __init__(self, transaction: Transaction, message: BaseMessage):
90
+ super().__init__(transaction)
91
+ self.message = message
harp/asgi/kernel.py ADDED
@@ -0,0 +1,164 @@
1
+ import traceback
2
+ from inspect import signature
3
+
4
+ from asgiref.typing import ASGIReceiveCallable, ASGISendCallable, Scope
5
+ from whistle import AsyncEventDispatcher, Event, IAsyncEventDispatcher
6
+
7
+ from harp import get_logger
8
+ from harp.controllers import DefaultControllerResolver
9
+ from harp.http import AlreadyHandledHttpResponse, HttpRequest, HttpResponse
10
+
11
+ from .bridge.requests import HttpRequestAsgiBridge
12
+ from .bridge.responses import HttpResponseAsgiBridge
13
+ from .events import (
14
+ EVENT_CONTROLLER_VIEW,
15
+ EVENT_CORE_CONTROLLER,
16
+ EVENT_CORE_REQUEST,
17
+ EVENT_CORE_RESPONSE,
18
+ EVENT_CORE_STARTED,
19
+ ControllerEvent,
20
+ ControllerViewEvent,
21
+ RequestEvent,
22
+ ResponseEvent,
23
+ )
24
+
25
+ logger = get_logger(__name__)
26
+
27
+
28
+ class ASGIKernel:
29
+ dispatcher: IAsyncEventDispatcher
30
+
31
+ def __init__(self, *, dispatcher=None, resolver=None, debug=False, handle_errors=True):
32
+ self.dispatcher = dispatcher or AsyncEventDispatcher()
33
+ # TODO IControllerResolver ? What contract do we expect ?
34
+ self.resolver = resolver or DefaultControllerResolver()
35
+ self.started = False
36
+ self.debug = debug
37
+ self.handle_errors = handle_errors
38
+
39
+ async def __call__(self, scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable):
40
+ asgi_type = scope.get("type", None)
41
+
42
+ if asgi_type == "http":
43
+ response = await self.handle_http(scope, receive, send)
44
+ if isinstance(response, AlreadyHandledHttpResponse):
45
+ return
46
+ return await HttpResponseAsgiBridge(response, send).send()
47
+
48
+ if asgi_type == "lifespan":
49
+ await receive()
50
+ await self.dispatcher.adispatch(EVENT_CORE_STARTED, Event())
51
+ self.started = True
52
+ return
53
+
54
+ if asgi_type == "websocket":
55
+ # NOT IMPLEMENTED YET!
56
+ # This is ignored here to avoid huge errors in the console.
57
+ return
58
+
59
+ raise RuntimeError(f'Unable to handle request, invalid type "{asgi_type}".')
60
+
61
+ def _resolve_arguments(self, subject, **candidates):
62
+ """
63
+ Dynamicaly resolve arguments by names in prototype, looking at a "candidates" dictionary that
64
+ contains the possible arguments used.
65
+ """
66
+
67
+ while hasattr(subject, "_mock_wraps"):
68
+ subject = subject._mock_wraps
69
+
70
+ try:
71
+ return [
72
+ candidates[name] if name in candidates or param.default is param.empty else param.default
73
+ for name, param in signature(subject).parameters.items()
74
+ if param.kind is not param.KEYWORD_ONLY
75
+ ], {}
76
+ except KeyError as exc:
77
+ raise TypeError(f"Unable to resolve arguments: {exc}")
78
+
79
+ async def _resolve_http_controller(self, request: HttpRequest):
80
+ """
81
+ Resolves the controller for the given request, aka the callable that will handle the request. If we fail to do
82
+ so, we will have hard time building a response for the user, and thus we raise an error.
83
+
84
+ Resolving the controller involves two steps:
85
+ - first, we use `self.resolver` to find a controller for the request.
86
+ See `harp.controllers.ControllerResolver`
87
+
88
+ :param request:
89
+ :return:
90
+ """
91
+ controller = await self.resolver.resolve(request)
92
+ if not controller:
93
+ raise RuntimeError("Unable to find request controller using resolver.")
94
+
95
+ event = ControllerEvent(request, controller)
96
+ await self.dispatcher.adispatch(EVENT_CORE_CONTROLLER, event)
97
+
98
+ if not event.controller:
99
+ raise RuntimeError("Unable to find request controller after controller event dispatch.")
100
+
101
+ return event.controller
102
+
103
+ async def _execute_http_controller(self, controller, request: HttpRequest, send: ASGISendCallable):
104
+ """Executes the given controller and make all efforts to build a http response."""
105
+ args, kwargs = self._resolve_arguments(controller, request=request, asgi_send=send)
106
+ response = await controller(*args, **kwargs)
107
+
108
+ # if the controller returned anything that is not a response, maybe some view listener can create one from this
109
+ # value (for example for json data, etc.).
110
+ if not isinstance(response, HttpResponse):
111
+ event = ControllerViewEvent(request, response)
112
+ await self.dispatcher.adispatch(EVENT_CONTROLLER_VIEW, event)
113
+ response = event.response
114
+
115
+ # if after the view event has been dispatched we still don't have a response, we have to raise an error.
116
+ if not isinstance(response, HttpResponse):
117
+ raise RuntimeError(
118
+ f"Response did not start despite the efforts made (controller return value is "
119
+ f"{type(response).__name__} after controller view event was dispatched)."
120
+ )
121
+
122
+ # the core response event may want to filter the response, for example to add some headers, etc.
123
+ await self.dispatcher.adispatch(EVENT_CORE_RESPONSE, ResponseEvent(request, response))
124
+
125
+ return response
126
+
127
+ async def handle_http(self, scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable):
128
+ request = HttpRequest(HttpRequestAsgiBridge(scope, receive))
129
+
130
+ if not self.started:
131
+ return HttpResponse(
132
+ "Unhandled server error: Cannot access service provider, the lifespan.startup asgi event "
133
+ "probably never went through.",
134
+ status=500,
135
+ content_type="text/plain",
136
+ )
137
+
138
+ try:
139
+ return await self.do_handle_http(request, send)
140
+
141
+ except Exception as exc:
142
+ if not self.handle_errors:
143
+ raise
144
+
145
+ # todo refactor this
146
+ logger.exception()
147
+ if self.debug:
148
+ return HttpResponse(
149
+ f"<h1>Internal Server Error</h1><h2>{type(exc).__name__}: {exc}</h2> "
150
+ f"<pre>{traceback.format_exc()}</pre>",
151
+ status=500,
152
+ content_type="text/html",
153
+ )
154
+ return HttpResponse("Internal Server Error", status=500, content_type="text/plain")
155
+
156
+ async def do_handle_http(self, request: HttpRequest, send: ASGISendCallable):
157
+ event = RequestEvent(request)
158
+ await self.dispatcher.adispatch(EVENT_CORE_REQUEST, event)
159
+
160
+ if event.controller:
161
+ return await self._execute_http_controller(event.controller, request, send)
162
+
163
+ controller = await self._resolve_http_controller(request)
164
+ return await self._execute_http_controller(controller, request, send)
File without changes