Skip to content

Commit 7364a5a

Browse files
Merge pull request #1 from PrivateBasicsApp/chore/update-dependencies
Chore/update dependencies (Django 5.2, FastAPI 0.138 + security)etesync#205
2 parents 5c14d56 + 25ca942 commit 7364a5a

13 files changed

Lines changed: 1458 additions & 111 deletions

File tree

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/journal
22
/db.sqlite3*
33
Session.vim
4-
/.venv
4+
/.venv*
55
/assets
66
/logs
77
/.coverage
@@ -10,6 +10,8 @@ Session.vim
1010
/.idea
1111

1212
__pycache__
13+
.pytest_cache/
14+
.ruff_cache/
1315
.*.swp
1416

1517
/etebase_server_settings.py
@@ -18,3 +20,4 @@ __pycache__
1820
/build
1921
/dist
2022
/*.egg-info
23+
.DS_Store

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ An [Etebase](https://www.etebase.com) (EteSync 2.0) server so you can run your o
1111

1212
## Requirements
1313

14-
Etebase requires Python 3.7 or newer and has a few Python dependencies (listed in `requirements.in/base.txt`).
14+
Etebase requires Python 3.10 or newer and has a few Python dependencies (listed in `requirements.in/base.txt`).
1515

1616
## From source
1717

etebase_server/fastapi/dependencies.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from django.db.models import QuerySet
44
from django.utils import timezone
5-
from fastapi import Depends
5+
from fastapi import Depends, Path
66
from fastapi.security import APIKeyHeader
77

88
from etebase_server.django import models
@@ -74,7 +74,7 @@ def get_collection_queryset(user: UserType = Depends(get_authenticated_user)) ->
7474

7575

7676
@django_db_cleanup_decorator
77-
def get_collection(collection_uid: str, queryset: QuerySet = Depends(get_collection_queryset)) -> models.Collection:
77+
def get_collection(collection_uid: str = Path(...), queryset: QuerySet = Depends(get_collection_queryset)) -> models.Collection:
7878
return get_object_or_404(queryset, uid=collection_uid)
7979

8080

pyproject.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,9 @@ ignore = ["E203", "E501", "E711", "E712", "N803", "N815", "N818", "T201"]
2626

2727
[tool.ruff.lint.isort]
2828
combine-as-imports = true
29+
30+
[tool.pytest.ini_options]
31+
testpaths = ["tests"]
32+
pythonpath = ["."]
33+
addopts = "-ra"
34+
DJANGO_SETTINGS_MODULE = "etebase_server.settings"

requirements-dev.txt

Lines changed: 380 additions & 24 deletions
Large diffs are not rendered by default.

requirements.in/base.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
django>=4.0,<5.0
1+
django>=5.2,<6.0
22
msgpack
33
pynacl
4-
fastapi>=0.104
4+
fastapi==0.138.2
55
pydantic>=2.0.0
66
typing_extensions
77
uvicorn[standard]
88
aiofiles
9-
redis>=4.2.0rc1
9+
redis>=5.0

requirements.in/development.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
1+
# Keep dev-only deps aligned with the runtime pins in requirements.txt.
2+
--constraint ../requirements.txt
3+
14
coverage
25
pip-tools
6+
pytest
7+
pytest-django
38
pywatchman
49
ruff
510
mypy

requirements.txt

Lines changed: 711 additions & 80 deletions
Large diffs are not rendered by default.

tests/conftest.py

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
"""Shared fixtures for the FastAPI endpoint tests.
2+
3+
Provides:
4+
- ``app`` : the real ASGI app built by ``create_application`` (session-scoped).
5+
- ``api_request`` : a synchronous ASGI driver to issue msgpack requests without httpx.
6+
- ``user`` / ``auth_token`` : user and token created via the ORM (they need a transactional
7+
DB, because the sync endpoints run in the threadpool and must see committed data).
8+
"""
9+
10+
import asyncio
11+
import os
12+
13+
import msgpack
14+
import pytest
15+
from django.conf import settings
16+
17+
from etebase_server.fastapi.utils import msgpack_encode
18+
19+
20+
@pytest.fixture(scope="session")
21+
def app():
22+
# create_application mounts StaticFiles on STATIC_ROOT and installs TrustedHostMiddleware:
23+
# in tests the dir does not exist and ALLOWED_HOSTS is empty, so we set them before building.
24+
settings.ALLOWED_HOSTS = ["*"]
25+
os.makedirs(settings.STATIC_ROOT, exist_ok=True)
26+
27+
from etebase_server.fastapi.main import create_application
28+
29+
return create_application()
30+
31+
32+
@pytest.fixture
33+
def api_request(app):
34+
"""Drive a single HTTP request through the ASGI app and return ``(status, decoded_body)``.
35+
36+
The request body is msgpack-encoded; the response is msgpack-decoded (falling back to raw
37+
bytes for non-msgpack responses, e.g. FastAPI's default 401/403).
38+
"""
39+
40+
def _request(method, path, *, body=None, token=None):
41+
raw = msgpack_encode(body) if body is not None else b""
42+
url_path, _, query = path.partition("?")
43+
44+
headers = [(b"host", b"testserver"), (b"accept", b"application/msgpack")]
45+
if body is not None:
46+
headers.append((b"content-type", b"application/msgpack"))
47+
if token is not None:
48+
headers.append((b"authorization", f"Token {token}".encode()))
49+
50+
scope = {
51+
"type": "http",
52+
"asgi": {"version": "3.0", "spec_version": "2.3"},
53+
"http_version": "1.1",
54+
"method": method,
55+
"path": url_path,
56+
"raw_path": url_path.encode(),
57+
"query_string": query.encode(),
58+
"headers": headers,
59+
"scheme": "http",
60+
"server": ("testserver", 80),
61+
"client": ("testclient", 1),
62+
"root_path": "",
63+
}
64+
65+
async def receive():
66+
return {"type": "http.request", "body": raw, "more_body": False}
67+
68+
captured = {"status": None, "body": b""}
69+
70+
async def send(message):
71+
if message["type"] == "http.response.start":
72+
captured["status"] = message["status"]
73+
elif message["type"] == "http.response.body":
74+
captured["body"] += message.get("body", b"")
75+
76+
asyncio.run(app(scope, receive, send))
77+
78+
out = captured["body"]
79+
try:
80+
decoded = msgpack.unpackb(out, raw=False) if out else None
81+
except Exception:
82+
decoded = out
83+
return captured["status"], decoded
84+
85+
return _request
86+
87+
88+
@pytest.fixture
89+
def user(transactional_db):
90+
from etebase_server.myauth.models import get_typed_user_model
91+
92+
User = get_typed_user_model()
93+
return User.objects.create(username="testuser", email="testuser@example.com")
94+
95+
96+
@pytest.fixture
97+
def auth_token(user):
98+
from etebase_server.django.token_auth.models import AuthToken
99+
100+
return AuthToken.objects.create(user=user).key

tests/integration/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)