Skip to content

Commit a71befe

Browse files
authored
New version (#3)
* migrate to uv * add test deps * add database tools * update database connection * update testing tools
1 parent 7093da6 commit a71befe

File tree

15 files changed

+1310
-256
lines changed

15 files changed

+1310
-256
lines changed

README.md

Lines changed: 1 addition & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -11,56 +11,9 @@ pip install hexfrost-toolbox
1111

1212
## Usage
1313

14+
### Tools
1415

15-
### Create async generator from list
1616

17-
```python
18-
from toolbox import create_async_generator
19-
20-
21-
async def main():
22-
urls = ['https://example.com', 'https://example2.com']
23-
24-
async_iterable = create_async_generator(urls)
25-
26-
async for url in async_iterable:
27-
await do_something(url)
28-
29-
```
30-
31-
### Create async function from sync function
32-
33-
```python
34-
from toolbox import sync_to_async
35-
36-
def some_blocking_function():
37-
return 'Hello, World!'
38-
39-
async def main():
40-
async_function = sync_to_async(some_blocking_function)
41-
42-
result = await async_function()
43-
44-
print(result)
45-
46-
```
47-
48-
### Create sync function from async function
49-
50-
```python
51-
from toolbox import async_to_sync
52-
53-
async def some_async_function():
54-
return 'Hello, World!'
55-
56-
def main():
57-
sync_function = async_to_sync(some_async_function)
58-
59-
result = sync_function()
60-
61-
print(result)
62-
63-
```
6417

6518
That's it! Enjoy! 🚀
6619

poetry.lock

Lines changed: 0 additions & 189 deletions
This file was deleted.

pyproject.toml

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,38 @@
1-
[tool.poetry]
2-
name = "hexfrost-toolbox"
3-
version = "0.1.3"
4-
description = "Open source library with useful utils for fast development"
5-
authors = ["Kaziamov <[email protected]>"]
6-
readme = "README.md"
7-
packages = [
8-
{ include = "toolbox" },
1+
[tool.pdm.build]
2+
includes = [
3+
"toolbox",
94
]
5+
[build-system]
6+
requires = ["pdm-backend"]
7+
build-backend = "pdm.backend"
108

11-
[tool.poetry.dependencies]
12-
python = ">=3.8.1,<4.0"
13-
hexfrost-simplecrud = ">=0.3.0"
149

10+
[project]
11+
authors = [
12+
{name = "Kaziamov", email = "[email protected]"},
13+
]
14+
requires-python = ">=3.9,<4.0"
15+
dependencies = [
16+
"cryptography>=44.0.3",
17+
"fastapi>=0.115.12",
18+
"pydantic>=2.10.6",
19+
"sqlalchemy>=2.0.40",
20+
]
21+
name = "hexfrost-toolbox"
22+
version = "0.1.4b"
23+
description = "Open source library with useful utils for fast development"
24+
readme = "README.md"
1525

16-
[build-system]
17-
requires = ["poetry-core"]
18-
build-backend = "poetry.core.masonry.api"
26+
[dependency-groups]
27+
dev = [
28+
"pytest==8.0.0",
29+
"pytest-asyncio==0.23.5",
30+
"pytest-mock==3.12.0",
31+
"pytest-cov>=6.0.0",
32+
"pre-commit>=4.1.0",
33+
"anyio[trio]>=3.7.1",
34+
"httpx==0.28.1",
35+
"asyncpg>=0.30.0",
36+
# "pytest-postgresql>=7.0.2",
37+
# "psycopg2-binary>=2.9.10",
38+
]

pytest.ini

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[pytest]
2+
asyncio_mode=auto

tests/__init__.py

Whitespace-only changes.

tests/fixtures/__init__.py

Whitespace-only changes.

tests/fixtures/database.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
from unittest.mock import MagicMock
2+
3+
import pytest
4+
from sqlalchemy.ext.asyncio import AsyncEngine
5+
6+
from toolbox.sqlalchemy.connection import DatabaseConnectionSettings, DatabaseConnectionManager
7+
8+
9+
@pytest.fixture
10+
def db_settings():
11+
class TestSettings(DatabaseConnectionSettings):
12+
POSTGRES_USER = "postgres"
13+
POSTGRES_PASSWORD = "postgres"
14+
POSTGRES_HOST = "0.0.0.0"
15+
POSTGRES_PORT = "5432"
16+
POSTGRES_DB = "test_postgres"
17+
18+
return TestSettings
19+
20+
21+
@pytest.fixture
22+
def mock_engine():
23+
return MagicMock(spec=AsyncEngine)
24+
25+
26+
@pytest.fixture(scope="function")
27+
def database_connector(db_settings):
28+
dc = DatabaseConnectionManager(settings=db_settings)
29+
return dc

tests/test_database.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
from unittest.mock import MagicMock, patch
2+
3+
import pytest
4+
from sqlalchemy import select
5+
from sqlalchemy.ext.asyncio import AsyncSession
6+
7+
from tests.fixtures.database import db_settings, mock_engine, database_connector
8+
9+
10+
@pytest.mark.asyncio
11+
async def test_database_manager_no_settings(database_connector):
12+
database_connector._settings = None
13+
database_connector._engine = None
14+
database_connector._async_sessionmaker = None
15+
16+
with pytest.raises(RuntimeError, match="No settings available"):
17+
database_connector._get_settings()
18+
19+
20+
def test_database_manager_set_engine(database_connector, mock_engine):
21+
database_connector.set_engine(mock_engine)
22+
assert database_connector._engine == mock_engine
23+
24+
25+
@patch('toolbox.sqlalchemy.connection.create_async_engine')
26+
def test_database_manager_get_engine_creates_new(mock_create_engine, database_connector, db_settings):
27+
database_connector._engine = None
28+
29+
expected_url = f"postgresql+asyncpg://{db_settings.POSTGRES_USER}:{db_settings.POSTGRES_PASSWORD}@{db_settings.POSTGRES_HOST}:{db_settings.POSTGRES_PORT}/{db_settings.POSTGRES_DB}"
30+
31+
database_connector.get_engine()
32+
33+
mock_create_engine.assert_called_once()
34+
call_args = mock_create_engine.call_args[0][0]
35+
assert call_args == expected_url
36+
37+
38+
def test_database_manager_get_engine_returns_existing(database_connector, mock_engine):
39+
database_connector.set_engine(mock_engine)
40+
result = database_connector.get_engine()
41+
assert result == mock_engine
42+
43+
44+
@patch('toolbox.sqlalchemy.connection.async_sessionmaker')
45+
def test_database_manager_get_session_maker(mock_sessionmaker, database_connector, mock_engine):
46+
database_connector.set_engine(mock_engine)
47+
database_connector._async_sessionmaker = None
48+
49+
database_connector.get_session_maker()
50+
51+
mock_sessionmaker.assert_called_once_with(
52+
mock_engine,
53+
expire_on_commit=False,
54+
class_=AsyncSession
55+
)
56+
57+
58+
def test_database_manager_get_session_maker_returns_existing(database_connector):
59+
mock_sessionmaker = MagicMock()
60+
database_connector._async_sessionmaker = mock_sessionmaker
61+
62+
result = database_connector.get_session_maker()
63+
assert result == mock_sessionmaker
64+
65+
66+
async def test_fastapi_depends_itegration_test(database_connector):
67+
from fastapi import Depends, FastAPI
68+
app = FastAPI()
69+
@app.get("/")
70+
async def index(database_conn = Depends(database_connector)):
71+
async with database_conn:
72+
res = await database_conn.scalar(select(1))
73+
return {"status": "ok"}
74+
75+
from httpx import ASGITransport, AsyncClient
76+
async with AsyncClient(
77+
transport=ASGITransport(app=app), base_url=f"http://test",
78+
) as client:
79+
response1 = await client.get('/')
80+
assert response1.status_code == 200
81+
82+
83+
84+
85+
async def test_get_db_connect_context_works(database_connector):
86+
async with database_connector.get_db_session() as conn:
87+
res = await conn.scalar(select(1))

tests/test_testing.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from sqlalchemy import select
2+
3+
from tests.fixtures.database import database_connector
4+
5+
6+
async def test_fastapi_depends_itegration_test_2(database_connector):
7+
from fastapi import Depends, FastAPI
8+
app = FastAPI()
9+
@app.get("/")
10+
async def index(database_conn = Depends(database_connector)):
11+
async with database_conn:
12+
res = await database_conn.scalar(select(1))
13+
return {"status": "ok"}
14+
15+
from toolbox.testing import debug_client
16+
async with debug_client(app) as client:
17+
response1 = await client.get('/')
18+
assert response1.status_code == 200

toolbox/__init__.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +0,0 @@
1-
import simplecrud as crud
2-
from toolbox.decorators import async_to_sync, sync_to_async
3-
from toolbox.utils import create_async_generator
4-
5-
__all__ = ["crud", "async_to_sync", "sync_to_async", "create_async_generator"]

0 commit comments

Comments
 (0)