haiway 0.12.1__tar.gz → 0.13.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. {haiway-0.12.1 → haiway-0.13.0}/LICENSE +2 -2
  2. {haiway-0.12.1 → haiway-0.13.0}/PKG-INFO +5 -5
  3. {haiway-0.12.1 → haiway-0.13.0}/README.md +2 -2
  4. {haiway-0.12.1 → haiway-0.13.0}/examples/fastAPI/Dockerfile +27 -1
  5. {haiway-0.12.1 → haiway-0.13.0}/examples/fastAPI/Makefile +6 -2
  6. haiway-0.13.0/examples/fastAPI/README.md +3 -0
  7. {haiway-0.12.1 → haiway-0.13.0}/examples/fastAPI/config/unit.json +3 -3
  8. {haiway-0.12.1 → haiway-0.13.0}/examples/fastAPI/docker-compose.yml +25 -1
  9. {haiway-0.12.1 → haiway-0.13.0}/examples/fastAPI/pyproject.toml +5 -8
  10. haiway-0.13.0/examples/fastAPI/src/features/todos/__init__.py +5 -0
  11. {haiway-0.12.1 → haiway-0.13.0}/examples/fastAPI/src/features/todos/state.py +11 -0
  12. haiway-0.13.0/examples/fastAPI/src/features/todos/user_tasks.py +19 -0
  13. {haiway-0.12.1 → haiway-0.13.0}/examples/fastAPI/src/integrations/postgres/__init__.py +2 -2
  14. {haiway-0.12.1 → haiway-0.13.0}/examples/fastAPI/src/integrations/postgres/client.py +59 -34
  15. haiway-0.13.0/examples/fastAPI/src/integrations/postgres/state.py +77 -0
  16. haiway-0.13.0/examples/fastAPI/src/integrations/postgres/types.py +69 -0
  17. haiway-0.13.0/examples/fastAPI/src/migrations/__init__.py +3 -0
  18. haiway-0.13.0/examples/fastAPI/src/migrations/__main__.py +26 -0
  19. haiway-0.13.0/examples/fastAPI/src/migrations/postgres/__init__.py +5 -0
  20. haiway-0.13.0/examples/fastAPI/src/migrations/postgres/execution.py +80 -0
  21. haiway-0.13.0/examples/fastAPI/src/migrations/postgres/migration_0.py +17 -0
  22. haiway-0.13.0/examples/fastAPI/src/migrations/postgres/types.py +9 -0
  23. {haiway-0.12.1 → haiway-0.13.0}/examples/fastAPI/src/server/application.py +2 -2
  24. {haiway-0.12.1 → haiway-0.13.0}/examples/fastAPI/src/server/middlewares/context.py +2 -2
  25. {haiway-0.12.1 → haiway-0.13.0}/examples/fastAPI/src/server/routes/todos.py +9 -3
  26. haiway-0.13.0/examples/fastAPI/src/solutions/user_tasks/__init__.py +7 -0
  27. haiway-0.13.0/examples/fastAPI/src/solutions/user_tasks/postgres.py +218 -0
  28. haiway-0.13.0/examples/fastAPI/src/solutions/user_tasks/state.py +77 -0
  29. {haiway-0.12.1 → haiway-0.13.0}/examples/fastAPI/src/solutions/user_tasks/types.py +3 -2
  30. {haiway-0.12.1 → haiway-0.13.0}/examples/fastAPI/uv.lock +65 -52
  31. {haiway-0.12.1 → haiway-0.13.0}/guidelines/functionalities.md +45 -20
  32. {haiway-0.12.1 → haiway-0.13.0}/guidelines/packages.md +8 -8
  33. {haiway-0.12.1 → haiway-0.13.0}/junit/test-results.xml +1 -1
  34. {haiway-0.12.1 → haiway-0.13.0}/pyproject.toml +2 -2
  35. haiway-0.13.0/src/haiway/helpers/caching.py +455 -0
  36. {haiway-0.12.1 → haiway-0.13.0}/tests/test_cache.py +0 -10
  37. {haiway-0.12.1 → haiway-0.13.0}/uv.lock +63 -63
  38. haiway-0.12.1/examples/fastAPI/README.md +0 -1
  39. haiway-0.12.1/examples/fastAPI/src/features/todos/__init__.py +0 -5
  40. haiway-0.12.1/examples/fastAPI/src/features/todos/calls.py +0 -16
  41. haiway-0.12.1/examples/fastAPI/src/features/todos/user_tasks.py +0 -17
  42. haiway-0.12.1/examples/fastAPI/src/integrations/postgres/state.py +0 -64
  43. haiway-0.12.1/examples/fastAPI/src/integrations/postgres/types.py +0 -27
  44. haiway-0.12.1/examples/fastAPI/src/solutions/user_tasks/__init__.py +0 -12
  45. haiway-0.12.1/examples/fastAPI/src/solutions/user_tasks/calls.py +0 -54
  46. haiway-0.12.1/examples/fastAPI/src/solutions/user_tasks/postgres.py +0 -106
  47. haiway-0.12.1/examples/fastAPI/src/solutions/user_tasks/state.py +0 -25
  48. haiway-0.12.1/src/haiway/helpers/caching.py +0 -356
  49. {haiway-0.12.1 → haiway-0.13.0}/.github/workflows/ci.yml +0 -0
  50. {haiway-0.12.1 → haiway-0.13.0}/.github/workflows/publish.yml +0 -0
  51. {haiway-0.12.1 → haiway-0.13.0}/.gitignore +0 -0
  52. {haiway-0.12.1 → haiway-0.13.0}/Makefile +0 -0
  53. {haiway-0.12.1 → haiway-0.13.0}/config/pre-push +0 -0
  54. {haiway-0.12.1 → haiway-0.13.0}/examples/fastAPI/.dockerignore +0 -0
  55. {haiway-0.12.1 → haiway-0.13.0}/examples/fastAPI/config/.env.example +0 -0
  56. {haiway-0.12.1 → haiway-0.13.0}/examples/fastAPI/src/features/__int__.py +0 -0
  57. {haiway-0.12.1 → haiway-0.13.0}/examples/fastAPI/src/features/todos/config.py +0 -0
  58. {haiway-0.12.1 → haiway-0.13.0}/examples/fastAPI/src/features/todos/types.py +0 -0
  59. {haiway-0.12.1 → haiway-0.13.0}/examples/fastAPI/src/integrations/__init__.py +0 -0
  60. {haiway-0.12.1 → haiway-0.13.0}/examples/fastAPI/src/integrations/postgres/config.py +0 -0
  61. {haiway-0.12.1 → haiway-0.13.0}/examples/fastAPI/src/server/__init__.py +0 -0
  62. {haiway-0.12.1 → haiway-0.13.0}/examples/fastAPI/src/server/__main__.py +0 -0
  63. {haiway-0.12.1 → haiway-0.13.0}/examples/fastAPI/src/server/config.py +0 -0
  64. {haiway-0.12.1 → haiway-0.13.0}/examples/fastAPI/src/server/middlewares/__init__.py +0 -0
  65. {haiway-0.12.1 → haiway-0.13.0}/examples/fastAPI/src/server/routes/__init__.py +0 -0
  66. {haiway-0.12.1 → haiway-0.13.0}/examples/fastAPI/src/server/routes/technical.py +0 -0
  67. {haiway-0.12.1 → haiway-0.13.0}/examples/fastAPI/src/solutions/__init__.py +0 -0
  68. {haiway-0.12.1 → haiway-0.13.0}/examples/fastAPI/src/solutions/user_tasks/config.py +0 -0
  69. {haiway-0.12.1 → haiway-0.13.0}/src/haiway/__init__.py +0 -0
  70. {haiway-0.12.1 → haiway-0.13.0}/src/haiway/context/__init__.py +0 -0
  71. {haiway-0.12.1 → haiway-0.13.0}/src/haiway/context/access.py +0 -0
  72. {haiway-0.12.1 → haiway-0.13.0}/src/haiway/context/disposables.py +0 -0
  73. {haiway-0.12.1 → haiway-0.13.0}/src/haiway/context/identifier.py +0 -0
  74. {haiway-0.12.1 → haiway-0.13.0}/src/haiway/context/logging.py +0 -0
  75. {haiway-0.12.1 → haiway-0.13.0}/src/haiway/context/metrics.py +0 -0
  76. {haiway-0.12.1 → haiway-0.13.0}/src/haiway/context/state.py +5 -5
  77. {haiway-0.12.1 → haiway-0.13.0}/src/haiway/context/tasks.py +0 -0
  78. {haiway-0.12.1 → haiway-0.13.0}/src/haiway/context/types.py +0 -0
  79. {haiway-0.12.1 → haiway-0.13.0}/src/haiway/helpers/__init__.py +0 -0
  80. {haiway-0.12.1 → haiway-0.13.0}/src/haiway/helpers/asynchrony.py +0 -0
  81. {haiway-0.12.1 → haiway-0.13.0}/src/haiway/helpers/metrics.py +0 -0
  82. {haiway-0.12.1 → haiway-0.13.0}/src/haiway/helpers/retries.py +0 -0
  83. {haiway-0.12.1 → haiway-0.13.0}/src/haiway/helpers/throttling.py +0 -0
  84. {haiway-0.12.1 → haiway-0.13.0}/src/haiway/helpers/timeouted.py +0 -0
  85. {haiway-0.12.1 → haiway-0.13.0}/src/haiway/helpers/tracing.py +0 -0
  86. {haiway-0.12.1 → haiway-0.13.0}/src/haiway/py.typed +0 -0
  87. {haiway-0.12.1 → haiway-0.13.0}/src/haiway/state/__init__.py +0 -0
  88. {haiway-0.12.1 → haiway-0.13.0}/src/haiway/state/attributes.py +0 -0
  89. {haiway-0.12.1 → haiway-0.13.0}/src/haiway/state/path.py +0 -0
  90. {haiway-0.12.1 → haiway-0.13.0}/src/haiway/state/requirement.py +0 -0
  91. {haiway-0.12.1 → haiway-0.13.0}/src/haiway/state/structure.py +0 -0
  92. {haiway-0.12.1 → haiway-0.13.0}/src/haiway/state/validation.py +0 -0
  93. {haiway-0.12.1 → haiway-0.13.0}/src/haiway/types/__init__.py +0 -0
  94. {haiway-0.12.1 → haiway-0.13.0}/src/haiway/types/default.py +0 -0
  95. {haiway-0.12.1 → haiway-0.13.0}/src/haiway/types/frozen.py +0 -0
  96. {haiway-0.12.1 → haiway-0.13.0}/src/haiway/types/missing.py +0 -0
  97. {haiway-0.12.1 → haiway-0.13.0}/src/haiway/utils/__init__.py +0 -0
  98. {haiway-0.12.1 → haiway-0.13.0}/src/haiway/utils/always.py +0 -0
  99. {haiway-0.12.1 → haiway-0.13.0}/src/haiway/utils/collections.py +0 -0
  100. {haiway-0.12.1 → haiway-0.13.0}/src/haiway/utils/env.py +0 -0
  101. {haiway-0.12.1 → haiway-0.13.0}/src/haiway/utils/freezing.py +0 -0
  102. {haiway-0.12.1 → haiway-0.13.0}/src/haiway/utils/logs.py +0 -0
  103. {haiway-0.12.1 → haiway-0.13.0}/src/haiway/utils/mimic.py +0 -0
  104. {haiway-0.12.1 → haiway-0.13.0}/src/haiway/utils/noop.py +0 -0
  105. {haiway-0.12.1 → haiway-0.13.0}/src/haiway/utils/queue.py +0 -0
  106. {haiway-0.12.1 → haiway-0.13.0}/tests/__init__.py +0 -0
  107. {haiway-0.12.1 → haiway-0.13.0}/tests/test_async_queue.py +0 -0
  108. {haiway-0.12.1 → haiway-0.13.0}/tests/test_attribute_path.py +0 -0
  109. {haiway-0.12.1 → haiway-0.13.0}/tests/test_auto_retry.py +0 -0
  110. {haiway-0.12.1 → haiway-0.13.0}/tests/test_context.py +0 -0
  111. {haiway-0.12.1 → haiway-0.13.0}/tests/test_state.py +0 -0
  112. {haiway-0.12.1 → haiway-0.13.0}/tests/test_streaming.py +0 -0
  113. {haiway-0.12.1 → haiway-0.13.0}/tests/test_timeout.py +0 -0
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2024 Miquido
3
+ Copyright (c) 2024-2025 Miquido
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
18
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
19
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
20
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
21
+ SOFTWARE.
@@ -1,13 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: haiway
3
- Version: 0.12.1
3
+ Version: 0.13.0
4
4
  Summary: Framework for dependency injection and state management within structured concurrency model.
5
5
  Project-URL: Homepage, https://miquido.com
6
6
  Project-URL: Repository, https://github.com/miquido/haiway.git
7
7
  Maintainer-email: Kacper Kaliński <kacper.kalinski@miquido.com>
8
8
  License: MIT License
9
9
 
10
- Copyright (c) 2024 Miquido
10
+ Copyright (c) 2024-2025 Miquido
11
11
 
12
12
  Permission is hereby granted, free of charge, to any person obtaining a copy
13
13
  of this software and associated documentation files (the "Software"), to deal
@@ -40,12 +40,12 @@ Requires-Dist: pyright~=1.1; extra == 'dev'
40
40
  Requires-Dist: pytest-asyncio~=0.23; extra == 'dev'
41
41
  Requires-Dist: pytest-cov~=4.1; extra == 'dev'
42
42
  Requires-Dist: pytest~=7.4; extra == 'dev'
43
- Requires-Dist: ruff~=0.9; extra == 'dev'
43
+ Requires-Dist: ruff~=0.11; extra == 'dev'
44
44
  Description-Content-Type: text/markdown
45
45
 
46
46
  # 🚗 haiway 🚕 🚚 🚙
47
47
 
48
- haiway is a framework helping to build better project codebase by leveraging concepts of structured concurrency and functional programming.
48
+ haiway is a framework designed to facilitate the development of applications using the functional programming paradigm combined with structured concurrency concepts. Unlike traditional object-oriented frameworks, haiway emphasizes immutability, pure functions, and context-based state management, enabling developers to build scalable and maintainable applications. By leveraging context managers combined with context vars, haiway ensures safe state propagation in concurrent environments and simplifies dependency injection through function implementation propagation.
49
49
 
50
50
  ## 🖥️ Install
51
51
 
@@ -65,7 +65,7 @@ We welcome any feedback and suggestions! Feel free to open an issue or pull requ
65
65
 
66
66
  MIT License
67
67
 
68
- Copyright (c) 2024 Miquido
68
+ Copyright (c) 2024-2025 Miquido
69
69
 
70
70
  Permission is hereby granted, free of charge, to any person obtaining a copy
71
71
  of this software and associated documentation files (the "Software"), to deal
@@ -1,6 +1,6 @@
1
1
  # 🚗 haiway 🚕 🚚 🚙
2
2
 
3
- haiway is a framework helping to build better project codebase by leveraging concepts of structured concurrency and functional programming.
3
+ haiway is a framework designed to facilitate the development of applications using the functional programming paradigm combined with structured concurrency concepts. Unlike traditional object-oriented frameworks, haiway emphasizes immutability, pure functions, and context-based state management, enabling developers to build scalable and maintainable applications. By leveraging context managers combined with context vars, haiway ensures safe state propagation in concurrent environments and simplifies dependency injection through function implementation propagation.
4
4
 
5
5
  ## 🖥️ Install
6
6
 
@@ -20,7 +20,7 @@ We welcome any feedback and suggestions! Feel free to open an issue or pull requ
20
20
 
21
21
  MIT License
22
22
 
23
- Copyright (c) 2024 Miquido
23
+ Copyright (c) 2024-2025 Miquido
24
24
 
25
25
  Permission is hereby granted, free of charge, to any person obtaining a copy
26
26
  of this software and associated documentation files (the "Software"), to deal
@@ -12,7 +12,7 @@ COPY ./src/features ./src/features
12
12
  COPY ./src/server ./src/server
13
13
 
14
14
  # install dependencies and packages
15
- COPY --from=ghcr.io/astral-sh/uv:0.5.14 /uv /uvx /bin/
15
+ COPY --from=ghcr.io/astral-sh/uv:0.6.8 /uv /uvx /bin/
16
16
 
17
17
  ENV UV_PROJECT_ENVIRONMENT="/usr/local/"
18
18
 
@@ -35,3 +35,29 @@ COPY ./config/unit.json /docker-entrypoint.d/config.json
35
35
  CMD ["unitd", "--no-daemon", "--log", "/dev/stdout"]
36
36
 
37
37
  # port 80 is already exposed by nginx unit image, can't change it...
38
+
39
+ # MIGRATIONS #
40
+
41
+ FROM python:${PYTHON_TAG} AS migrations_builder
42
+
43
+ # copy only the parts needed for production
44
+ COPY ./src/integrations ./src/integrations
45
+ COPY ./src/solutions ./src/solutions
46
+ COPY ./src/features ./src/features
47
+ COPY ./src/migrations ./src/migrations
48
+ # install dependencies and packages
49
+ COPY --from=ghcr.io/astral-sh/uv:0.6.8 /uv /uvx /bin/
50
+
51
+ ENV UV_PROJECT_ENVIRONMENT="/usr/local/"
52
+
53
+ RUN --mount=type=bind,source=./uv.lock,target=./uv.lock --mount=type=bind,source=./pyproject.toml,target=./pyproject.toml uv sync --python python${PYTHON_TAG} --locked --no-editable --no-python-downloads --link-mode copy --compile-bytecode --only-group server
54
+
55
+ FROM migrations_builder AS migrations
56
+
57
+ RUN apt-get update \
58
+ && apt-get upgrade -y \
59
+ && apt-get -y autoremove \
60
+ && apt-get clean \
61
+ && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
62
+
63
+ CMD ["python", "-m", "migrations"]
@@ -12,7 +12,7 @@ ifndef UV_VERSION
12
12
  UV_VERSION := 0.5.14
13
13
  endif
14
14
 
15
- .PHONY: uv_check venv sync update format lint run docker_run
15
+ .PHONY: uv_check venv sync update format lint run migrations docker_run
16
16
 
17
17
  # Check installed UV version and install if needed
18
18
  uv_check:
@@ -81,6 +81,10 @@ lint:
81
81
  run:
82
82
  @python -m server
83
83
 
84
- # Run all services locally using docker-compose.
84
+ # Execute migrations
85
+ migrations:
86
+ @python -m migrations
87
+
88
+ # Run all services locally using docker-compose.
85
89
  docker_run:
86
90
  @docker compose up --force-recreate --build server
@@ -0,0 +1,3 @@
1
+ ## Haiway FastAPI Example
2
+
3
+ Example of FastAPI HTTP server using haiway architecture.
@@ -19,19 +19,19 @@
19
19
  "uri": "/*"
20
20
  },
21
21
  "action": {
22
- "pass": "applications/server"
22
+ "pass": "applications/api"
23
23
  }
24
24
  }
25
25
  ]
26
26
  },
27
27
  "applications": {
28
- "server": {
28
+ "api": {
29
29
  "type": "python",
30
30
  "protocol": "asgi",
31
31
  "path": "/",
32
32
  "module": "server",
33
33
  "callable": "app",
34
- "processes": 1,
34
+ "processes": 2,
35
35
  "environment": {
36
36
  "PYTHONOPTIMIZE": "2",
37
37
  "HOME": "."
@@ -3,6 +3,8 @@ services:
3
3
  depends_on:
4
4
  postgres:
5
5
  condition: service_healthy
6
+ migrations:
7
+ condition: service_completed_successfully
6
8
  links:
7
9
  - postgres
8
10
  build:
@@ -22,6 +24,27 @@ services:
22
24
  POSTGRES_SSLMODE: ${POSTGRES_SSLMODE:-prefer}
23
25
  POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
24
26
 
27
+ migrations:
28
+ depends_on:
29
+ postgres:
30
+ condition: service_healthy
31
+ links:
32
+ - postgres
33
+ build:
34
+ context: .
35
+ dockerfile: Dockerfile
36
+ target: migrations
37
+ restart: "no"
38
+ environment:
39
+ PYTHONOPTIMIZE: ${PYTHONOPTIMIZE:-2}
40
+ DEBUG_LOGGING: ${DEBUG_LOGGING}
41
+ POSTGRES_DATABASE: ${POSTGRES_DATABASE}
42
+ POSTGRES_HOST: postgres
43
+ POSTGRES_PORT: 5432
44
+ POSTGRES_USER: ${POSTGRES_USER}
45
+ POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
46
+ POSTGRES_SSLMODE: ${POSTGRES_SSLMODE:-prefer}
47
+
25
48
  postgres:
26
49
  image: postgres:16.3-alpine3.20
27
50
  restart: always
@@ -34,7 +57,8 @@ services:
34
57
  ports:
35
58
  - ${POSTGRES_PORT}:5432
36
59
  healthcheck:
37
- test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DATABASE}"]
60
+ test:
61
+ ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DATABASE}"]
38
62
  interval: 10s
39
63
  start_period: 10s
40
64
  timeout: 10s
@@ -28,7 +28,7 @@ server = [
28
28
  dev = [
29
29
  "haiway_fastapi",
30
30
  "uvicorn~=0.30",
31
- "ruff~=0.8.0",
31
+ "ruff~=0.11",
32
32
  "pyright~=1.1",
33
33
  "bandit~=1.7",
34
34
  ]
@@ -53,13 +53,10 @@ exclude = ["**/node_modules", "**/__pycache__"]
53
53
  ignore = []
54
54
  stubPath = "./stubs"
55
55
  reportMissingImports = true
56
- reportMissingTypeStubs = true
57
- typeCheckingMode = "strict"
56
+ reportMissingTypeStubs = false
58
57
  userFileIndexingLimit = -1
59
58
  useLibraryCodeForTypes = true
60
59
 
61
- [tool.setuptools]
62
- include-package-data = true
63
-
64
- [tool.setuptools.packages.find]
65
- where = ["src"]
60
+ [tool.pyright.analysis]
61
+ diagnosticMode = "workspace"
62
+ typeCheckingMode = "strict"
@@ -0,0 +1,5 @@
1
+ from features.todos.state import Todos
2
+
3
+ __all__ = [
4
+ "Todos",
5
+ ]
@@ -1,4 +1,7 @@
1
+ from uuid import UUID
2
+
1
3
  from haiway import State
4
+ from haiway.context import ctx
2
5
 
3
6
  from features.todos.types import TodoCompletion
4
7
  from features.todos.user_tasks import complete_todo_task
@@ -9,4 +12,12 @@ __all__ = [
9
12
 
10
13
 
11
14
  class Todos(State):
15
+ @classmethod
16
+ async def complete_todo(
17
+ cls,
18
+ *,
19
+ identifier: UUID,
20
+ ) -> None:
21
+ await ctx.state(cls).complete(identifier=identifier)
22
+
12
23
  complete: TodoCompletion = complete_todo_task
@@ -0,0 +1,19 @@
1
+ from uuid import UUID
2
+
3
+ from solutions.user_tasks import UserTasks
4
+
5
+ __all__ = [
6
+ "complete_todo_task",
7
+ ]
8
+
9
+
10
+ async def complete_todo_task(
11
+ *,
12
+ identifier: UUID,
13
+ ) -> None:
14
+ match await UserTasks.fetch(identifier=identifier):
15
+ case None:
16
+ pass
17
+
18
+ case task:
19
+ await UserTasks.update(task=task.updated(completed=True))
@@ -1,11 +1,11 @@
1
- from integrations.postgres.client import PostgresClient
1
+ from integrations.postgres.client import PostgresConnectionPool
2
2
  from integrations.postgres.state import Postgres, PostgresConnection
3
3
  from integrations.postgres.types import PostgresException, PostgresRow, PostgresValue
4
4
 
5
5
  __all__ = [
6
6
  "Postgres",
7
- "PostgresClient",
8
7
  "PostgresConnection",
8
+ "PostgresConnectionPool",
9
9
  "PostgresException",
10
10
  "PostgresRow",
11
11
  "PostgresValue",
@@ -1,3 +1,4 @@
1
+ from collections.abc import Sequence
1
2
  from types import TracebackType
2
3
  from typing import final
3
4
 
@@ -9,18 +10,6 @@ from asyncpg import ( # pyright: ignore[reportMissingTypeStubs]
9
10
  from asyncpg.pool import PoolAcquireContext # pyright: ignore[reportMissingTypeStubs]
10
11
  from asyncpg.transaction import Transaction # pyright: ignore[reportMissingTypeStubs]
11
12
 
12
- from integrations.postgres.state import (
13
- PostgresConnection,
14
- PostgresConnectionContext,
15
- PostgresTransactionContext,
16
- )
17
- from integrations.postgres.types import PostgresException, PostgresRow, PostgresValue
18
-
19
- __all__ = [
20
- "PostgresClient",
21
- ]
22
-
23
-
24
13
  from integrations.postgres.config import (
25
14
  POSTGRES_DATABASE,
26
15
  POSTGRES_HOST,
@@ -29,15 +18,36 @@ from integrations.postgres.config import (
29
18
  POSTGRES_SSLMODE,
30
19
  POSTGRES_USER,
31
20
  )
32
- from integrations.postgres.state import Postgres
21
+ from integrations.postgres.state import (
22
+ Postgres,
23
+ PostgresConnection,
24
+ PostgresConnectionContext,
25
+ PostgresTransactionContext,
26
+ )
27
+ from integrations.postgres.types import (
28
+ PostgresException,
29
+ PostgresRow,
30
+ PostgresValue,
31
+ )
33
32
 
34
33
  __all__ = [
35
- "PostgresClient",
34
+ "PostgresConnectionPool",
36
35
  ]
37
36
 
38
37
 
39
38
  @final
40
- class PostgresClient:
39
+ class PostgresConnectionPool:
40
+ __slots__ = (
41
+ "_connection_limit",
42
+ "_database",
43
+ "_host",
44
+ "_password",
45
+ "_pool",
46
+ "_port",
47
+ "_ssl",
48
+ "_user",
49
+ )
50
+
41
51
  def __init__( # noqa: PLR0913
42
52
  self,
43
53
  host: str = POSTGRES_HOST,
@@ -48,20 +58,28 @@ class PostgresClient:
48
58
  ssl: str = POSTGRES_SSLMODE,
49
59
  connection_limit: int = 1,
50
60
  ) -> None:
51
- self._pool: Pool = create_pool(
52
- min_size=1,
53
- max_size=connection_limit,
54
- database=database,
55
- user=user,
56
- password=password,
57
- host=host,
58
- port=port,
59
- ssl=ssl,
60
- )
61
+ self._host: str = host
62
+ self._port: str = port
63
+ self._database: str = database
64
+ self._user: str = user
65
+ self._password: str = password
66
+ self._ssl: str = ssl
67
+ self._connection_limit: int = connection_limit
68
+ self._pool: Pool
61
69
 
62
70
  async def __aenter__(self) -> Postgres:
71
+ self._pool = create_pool(
72
+ min_size=1,
73
+ max_size=self._connection_limit,
74
+ database=self._database,
75
+ user=self._user,
76
+ password=self._password,
77
+ host=self._host,
78
+ port=self._port,
79
+ ssl=self._ssl,
80
+ )
63
81
  await self._pool # initialize pool
64
- return Postgres(connection=self.connection)
82
+ return Postgres(prepare_connection=self.prepare_connection)
65
83
 
66
84
  async def __aexit__(
67
85
  self,
@@ -72,12 +90,14 @@ class PostgresClient:
72
90
  if self._pool._initialized: # pyright: ignore[reportPrivateUsage]
73
91
  await self._pool.close()
74
92
 
75
- def connection(self) -> PostgresConnectionContext:
93
+ def prepare_connection(self) -> PostgresConnectionContext:
76
94
  return _ConnectionContext(pool_context=self._pool.acquire()) # pyright: ignore[reportUnknownMemberType]
77
95
 
78
96
 
79
97
  @final
80
98
  class _TransactionContext:
99
+ __slots__ = ("_transaction_context",)
100
+
81
101
  def __init__(
82
102
  self,
83
103
  transaction_context: Transaction,
@@ -102,6 +122,8 @@ class _TransactionContext:
102
122
 
103
123
  @final
104
124
  class _ConnectionContext:
125
+ __slots__ = ("_pool_context",)
126
+
105
127
  def __init__(
106
128
  self,
107
129
  pool_context: PoolAcquireContext,
@@ -115,12 +137,15 @@ class _ConnectionContext:
115
137
  statement: str,
116
138
  /,
117
139
  *args: PostgresValue,
118
- ) -> list[PostgresRow]:
140
+ ) -> Sequence[PostgresRow]:
119
141
  try:
120
- return await acquired_connection.fetch( # pyright: ignore[reportUnknownMemberType, reportUnknownVariableType]
121
- statement,
122
- *args,
123
- )
142
+ return [
143
+ dict(record) # convert to dict to allow match patterns
144
+ for record in await acquired_connection.fetch( # pyright: ignore[reportUnknownMemberType, reportUnknownVariableType]
145
+ statement,
146
+ *args,
147
+ )
148
+ ]
124
149
 
125
150
  except Exception as exc:
126
151
  raise PostgresException("Failed to execute SQL statement") from exc
@@ -129,8 +154,8 @@ class _ConnectionContext:
129
154
  return _TransactionContext(transaction_context=acquired_connection.transaction()) # pyright: ignore[reportUnknownMemberType, reportUnknownArgumentType]
130
155
 
131
156
  return PostgresConnection(
132
- execute=execute,
133
- transaction=transaction,
157
+ execute_statement=execute,
158
+ prepare_transaction=transaction,
134
159
  )
135
160
 
136
161
  async def __aexit__(
@@ -0,0 +1,77 @@
1
+ from collections.abc import Sequence
2
+ from typing import Any
3
+
4
+ from haiway import State, ctx
5
+
6
+ from integrations.postgres.types import (
7
+ PostgresConnectionContext,
8
+ PostgresConnectionPreparing,
9
+ PostgresRow,
10
+ PostgresStatementExecuting,
11
+ PostgresTransactionContext,
12
+ PostgresTransactionPreparing,
13
+ )
14
+
15
+ __all__ = [
16
+ "Postgres",
17
+ "PostgresConnection",
18
+ ]
19
+
20
+
21
+ class PostgresConnection(State):
22
+ execute_statement: PostgresStatementExecuting
23
+ prepare_transaction: PostgresTransactionPreparing
24
+
25
+ @classmethod
26
+ async def fetch_one(
27
+ cls,
28
+ statement: str,
29
+ /,
30
+ *args: Any,
31
+ ) -> PostgresRow | None:
32
+ return next(
33
+ (
34
+ result
35
+ for result in await ctx.state(cls).execute_statement(
36
+ statement,
37
+ *args,
38
+ )
39
+ ),
40
+ None,
41
+ )
42
+
43
+ @classmethod
44
+ async def fetch(
45
+ cls,
46
+ statement: str,
47
+ /,
48
+ *args: Any,
49
+ ) -> Sequence[PostgresRow]:
50
+ return await ctx.state(cls).execute_statement(
51
+ statement,
52
+ *args,
53
+ )
54
+
55
+ @classmethod
56
+ async def execute(
57
+ cls,
58
+ statement: str,
59
+ /,
60
+ *args: Any,
61
+ ) -> None:
62
+ await ctx.state(cls).execute_statement(
63
+ statement,
64
+ *args,
65
+ )
66
+
67
+ @classmethod
68
+ def transaction(cls) -> PostgresTransactionContext:
69
+ return ctx.state(cls).prepare_transaction()
70
+
71
+
72
+ class Postgres(State):
73
+ @classmethod
74
+ def connection(cls) -> PostgresConnectionContext:
75
+ return ctx.state(cls).prepare_connection()
76
+
77
+ prepare_connection: PostgresConnectionPreparing
@@ -0,0 +1,69 @@
1
+ from collections.abc import Mapping, Sequence
2
+ from datetime import date, datetime, time
3
+ from types import TracebackType
4
+ from typing import TYPE_CHECKING, Any, Protocol, runtime_checkable
5
+ from uuid import UUID
6
+
7
+ if TYPE_CHECKING:
8
+ from integrations.postgres.state import PostgresConnection
9
+
10
+ __all__ = [
11
+ "PostgresConnectionContext",
12
+ "PostgresConnectionPreparing",
13
+ "PostgresException",
14
+ "PostgresStatementExecuting",
15
+ "PostgresTransactionContext",
16
+ "PostgresTransactionPreparing",
17
+ "PostgresValue",
18
+ ]
19
+
20
+ type PostgresValue = UUID | datetime | date | time | str | bytes | float | int | bool | None
21
+ type PostgresRow = Mapping[str, Any]
22
+
23
+
24
+ @runtime_checkable
25
+ class PostgresStatementExecuting(Protocol):
26
+ async def __call__(
27
+ self,
28
+ statement: str,
29
+ /,
30
+ *args: PostgresValue,
31
+ ) -> Sequence[PostgresRow]: ...
32
+
33
+
34
+ @runtime_checkable
35
+ class PostgresTransactionContext(Protocol):
36
+ async def __aenter__(self) -> None: ...
37
+
38
+ async def __aexit__(
39
+ self,
40
+ exc_type: type[BaseException] | None,
41
+ exc_val: BaseException | None,
42
+ exc_tb: TracebackType | None,
43
+ ) -> bool | None: ...
44
+
45
+
46
+ @runtime_checkable
47
+ class PostgresTransactionPreparing(Protocol):
48
+ def __call__(self) -> PostgresTransactionContext: ...
49
+
50
+
51
+ @runtime_checkable
52
+ class PostgresConnectionContext(Protocol):
53
+ async def __aenter__(self) -> "PostgresConnection": ...
54
+
55
+ async def __aexit__(
56
+ self,
57
+ exc_type: type[BaseException] | None,
58
+ exc_val: BaseException | None,
59
+ exc_tb: TracebackType | None,
60
+ ) -> bool | None: ...
61
+
62
+
63
+ @runtime_checkable
64
+ class PostgresConnectionPreparing(Protocol):
65
+ def __call__(self) -> PostgresConnectionContext: ...
66
+
67
+
68
+ class PostgresException(Exception):
69
+ pass
@@ -0,0 +1,3 @@
1
+ from haiway import load_env
2
+
3
+ load_env()
@@ -0,0 +1,26 @@
1
+ from asyncio import run
2
+
3
+ from haiway import ctx, setup_logging
4
+
5
+ from integrations.postgres import PostgresConnectionPool
6
+ from migrations.postgres import execute_postgres_migrations
7
+
8
+
9
+ async def migrate_databases() -> None:
10
+ async with ctx.scope(
11
+ "migrations",
12
+ disposables=[
13
+ PostgresConnectionPool(),
14
+ ],
15
+ ):
16
+ ctx.log_warning("Running postgres migrations...")
17
+ await execute_postgres_migrations()
18
+ ctx.log_info("...postgres migrations completed")
19
+
20
+
21
+ def main() -> None:
22
+ setup_logging("migrations")
23
+ run(migrate_databases())
24
+
25
+
26
+ main()
@@ -0,0 +1,5 @@
1
+ from migrations.postgres.execution import execute_postgres_migrations
2
+
3
+ __all__ = [
4
+ "execute_postgres_migrations",
5
+ ]