Skip to content

Commit 91ba7fc

Browse files
committed
feat: Add uv and more tests
1 parent 94aa172 commit 91ba7fc

35 files changed

Lines changed: 1112 additions & 1059 deletions

Dockerfile

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@ FROM python:3.10.14-alpine
22

33
WORKDIR /app
44

5-
COPY pyproject.toml poetry.lock* /app/
5+
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
6+
RUN chmod +x /usr/local/bin/uv
7+
8+
COPY pyproject.toml /app/
69

710
RUN apk update && \
811
apk add build-base postgresql-dev && \
9-
pip install --no-cache-dir --upgrade pip && \
10-
pip install poetry && \
11-
poetry install --no-root --with dev && \
12+
uv sync --all-groups && \
1213
apk del --purge build-base
1314

1415
ADD . /app

MANIFEST

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
1+
# Include required files
2+
include README.md
3+
include LICENSE
4+
5+
# Include package files
6+
recursive-include sqlalchemy_serializer *.py
7+
18
# Exclude the local virtual environment directory if it's named .venv
29
prune .venv
310

411
# Exclude dev/test tools
512
prune .git
6-
prune pyproject.toml
13+
prune pyproject.toml

Makefile

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
.PHONY: test
1+
.PHONY: test format
22
FILE = $(file)
33

44
test:
55
TEST_FILE=$(FILE) docker-compose up --build --abort-on-container-exit
66

77
format:
8-
black .
8+
uv run ruff format .
9+
uv run ruff check --fix .

docker-compose.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,10 @@ services:
1111
build: .
1212
command:
1313
[
14-
"poetry",
14+
"uv",
1515
"run",
1616
"pytest",
1717
"-vv",
18-
"--pylama",
1918
"--cov=sqlalchemy_serializer",
2019
"--cov-report",
2120
"term-missing",

docs/DEVELOPMENT.md

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
- **Python**: 3.10+ (specified in `pyproject.toml`)
88
- **Docker & Docker Compose**: Required for running tests with PostgreSQL
9-
- **Poetry**: Dependency management (installed via Docker or locally)
9+
- **uv**: Dependency management (installed via Docker or locally)
1010

1111
### Local Development Setup
1212

@@ -16,14 +16,14 @@
1616
cd SQLAlchemy-serializer
1717
```
1818

19-
2. **Install dependencies with Poetry**
19+
2. **Install dependencies with uv**
2020
```bash
21-
poetry install --with dev
21+
uv pip install -e ".[dev]"
2222
```
2323

24-
3. **Activate virtual environment**
24+
3. **Activate virtual environment** (if using uv's venv)
2525
```bash
26-
poetry shell
26+
source .venv/bin/activate # or .venv\Scripts\activate on Windows
2727
```
2828

2929
### Docker-based Development
@@ -70,14 +70,14 @@ The Docker setup includes:
7070
make test
7171
```
7272

73-
4. **Format code**
73+
4. **Format and lint code**
7474
```bash
75-
make format # Runs black formatter
75+
make format # Runs ruff format and ruff check --fix
7676
```
7777

78-
5. **Check linting**
78+
5. **Check linting only (no auto-fix)**
7979
```bash
80-
poetry run pylama .
80+
uv run ruff check .
8181
```
8282

8383
6. **Commit changes**
@@ -401,10 +401,9 @@ print(serializer.schema._tree) # Inspect rule tree
401401
### Updating Dependencies
402402

403403
1. **Update version in `pyproject.toml`**
404-
2. **Update lock file**:
404+
2. **Update dependencies**:
405405
```bash
406-
poetry update <package>
407-
poetry lock
406+
uv pip install -e ".[dev]" --upgrade
408407
```
409408
3. **Test compatibility**:
410409
```bash
@@ -436,7 +435,7 @@ print(serializer.schema._tree) # Inspect rule tree
436435

437436
Version is specified in `pyproject.toml`:
438437
```toml
439-
[tool.poetry]
438+
[project]
440439
version = "1.4.22"
441440
```
442441

@@ -449,7 +448,7 @@ Follow [Semantic Versioning](https://semver.org/):
449448

450449
- [ ] All tests pass (`make test`)
451450
- [ ] Code coverage ≥ 80%
452-
- [ ] No linting errors (`poetry run pylama .`)
451+
- [ ] No linting errors (`uv run ruff check .`)
453452
- [ ] Code formatted (`make format`)
454453
- [ ] Documentation updated (README.md, ARCHITECTURE.md, DEVELOPMENT.md, KNOWLEDGE.md)
455454
- [ ] CHANGELOG updated (if maintained)
@@ -469,12 +468,12 @@ Follow [Semantic Versioning](https://semver.org/):
469468

470469
3. **Build package**:
471470
```bash
472-
poetry build
471+
uv build
473472
```
474473

475474
4. **Publish to PyPI** (if maintainer):
476475
```bash
477-
poetry publish
476+
uv publish
478477
```
479478

480479
5. **Push to repository**:

poetry.lock

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

pyproject.toml

Lines changed: 106 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,122 @@
1-
[tool.poetry]
1+
[project]
22
name = "sqlalchemy-serializer"
33
version = "1.4.22"
44
description = "Mixin for SQLAlchemy models serialization without pain"
5-
authors = ["yuri.boiko <yuri.boiko.dev@gmail.com>"]
6-
license = "MIT"
7-
readme = "README.md"
8-
include = [
9-
"README.md",
10-
"LICENSE",
11-
"sqlalchemy_serializer/**"
5+
authors = [
6+
{name = "yuri.boiko", email = "yuri.boiko.dev@gmail.com"}
127
]
8+
license = {text = "MIT"}
9+
readme = "README.md"
10+
requires-python = ">=3.10"
1311
classifiers = [
1412
"Programming Language :: Python :: 3",
15-
"License :: OSI Approved :: Apache Software License",
13+
"License :: OSI Approved :: MIT License",
1614
"Operating System :: OS Independent",
1715
]
1816
keywords = ["sqlalchemy", "serialize", "to_dict", "JSON"]
19-
repository = "https://github.com/n0nSmoker/SQLAlchemy-serializer"
17+
dependencies = [
18+
"SQLAlchemy==2.0.45",
19+
"psycopg2-binary==2.9.11",
20+
"pytz>=2025.2",
21+
"setuptools>=80.9.0",
22+
]
23+
24+
[dependency-groups]
25+
lint = [
26+
"ruff>=0.9.4",
27+
"isort>=6.0.1",
28+
]
29+
test = [
30+
"pytest==9.0.1",
31+
"pytest-cov==7.0.0",
32+
"requests==2.32.5",
33+
"pytest-mock>=3.15.1",
34+
"tomli>=2.0.1; python_version < '3.11'",
35+
]
2036

21-
[tool.poetry.dependencies]
22-
python = "^3.10"
23-
SQLAlchemy = "2.0.29"
24-
psycopg2-binary = "2.9.9"
25-
pytz = "^2024.1"
26-
setuptools = "^70.1.1"
27-
28-
[tool.poetry.group.dev.dependencies]
29-
pytest = "8.1.1"
30-
pytest-cov = "5.0.0"
31-
pylama = "8.4.1"
32-
requests = "2.32.3"
33-
black = "^24.4.2"
34-
pytest-mock = "^3.14.0"
37+
[project.urls]
38+
repository = "https://github.com/n0nSmoker/SQLAlchemy-serializer"
3539

3640
[build-system]
37-
requires = ["poetry-core"]
38-
build-backend = "poetry.core.masonry.api"
41+
requires = ["setuptools>=80.9.0", "wheel"]
42+
build-backend = "setuptools.build_meta"
43+
44+
[tool.setuptools]
45+
include-package-data = true
3946

4047
[tool.pytest.ini_options]
4148
addopts = "-xvrs --color=yes"
4249
log_cli = true
50+
51+
52+
[tool.ruff]
53+
line-length = 95
54+
55+
# Exclude a variety of commonly ignored directories
56+
exclude = [
57+
".git",
58+
".ruff_cache",
59+
".venv",
60+
"__pypackages__",
61+
"dist",
62+
]
63+
64+
lint.select = [
65+
"YTT", # flake8-2020
66+
"E", # pycodestyle
67+
"F", # pyflakes
68+
"I", # isort
69+
"ASYNC", # flake8-async
70+
"BLE", # flake8-blind-except
71+
"B", # flake8-bugbear
72+
"COM", # flake8-commas
73+
"W", # pycodestyle warnings
74+
"TC", # flake8-type-checking
75+
"ANN", # flake8-annotations
76+
"C90", # mccabe
77+
"PL", # pylint
78+
"ICN", # flake8-import-conventions
79+
"LOG", # flake8-logging
80+
"G", # flake8-logging-format
81+
# "INP", # flake8-no-pep420
82+
"PIE", # flake8-pie
83+
"PYI", # flake8-pyi
84+
"RSE", # flake8-raise
85+
"RET", # flake8-return
86+
"SIM", # flake8-simplify
87+
"SLOT", # flake8-slots
88+
"TID", # flake8-tidy-imports
89+
"TC", # flake8-type-checking
90+
"ARG", # flake8-unused-arguments
91+
"NPY", # NumPy-specific rules
92+
"PERF", # Perflint (PERF)
93+
]
94+
# Allow unused variables when underscore-prefixed
95+
lint.dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
96+
lint.ignore = [
97+
"ANN002", # Missing type annotation for *args
98+
"ANN201", # Add return type annotation: `None`
99+
"ANN204", # Missing return type annotation for special method
100+
"ANN202", # Missing return type annotation for private function
101+
"ANN003", # Missing type annotation for `**kwargs`
102+
"ANN001", # Missing type annotation for function argument
103+
"B008", # Function calls in default function arguments.
104+
"COM812", # Checks for the absence of trailing commas
105+
"S101", # Use of `assert` detected
106+
"PLR0913", # Too many positional arguments for function call
107+
"PLR2004", # Magic value
108+
"PLW1508", # Invalid type for environment variable default; expected `str` or `None`
109+
"PYI024", # Use `typing.NamedTuple` instead of `collections.namedtuple`
110+
"SIM118", # Use `key in dict` instead of `key in dict.keys()`
111+
]
112+
113+
[tool.ruff.format]
114+
quote-style = "double"
115+
indent-style = "space"
116+
117+
[tool.isort]
118+
profile = "black"
119+
line_length = 95
120+
121+
[tool.ruff.lint.isort]
122+
known-first-party = ["sqlalchemy_serializer"]

sqlalchemy_serializer/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
__all__ = ["SerializerMixin", "Serializer", "serialize_collection"]
22

33
from sqlalchemy_serializer.serializer import (
4-
SerializerMixin,
54
Serializer,
5+
SerializerMixin,
66
serialize_collection,
77
)

sqlalchemy_serializer/lib/fields.py

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,26 @@
11
import functools
2-
import typing as t
32
import inspect
3+
44
from sqlalchemy import inspect as sql_inspect
55

66

7-
def get_sql_field_names(model_instance) -> t.Set[str]:
8-
"""
9-
:return: set of sql fields names
7+
def get_sql_field_names(model_instance) -> set[str]:
8+
""":return: set of sql fields names
109
:raise: sqlalchemy.exc.NoInspectionAvailable
1110
"""
1211
inspector = sql_inspect(model_instance)
1312
return {a.key for a in inspector.mapper.attrs}
1413

1514

16-
def get_property_field_names(model_instance) -> t.Set[str]:
17-
"""
18-
:return: set of field names defined as @property
19-
"""
15+
def get_property_field_names(model_instance) -> set[str]:
16+
""":return: set of field names defined as @property"""
2017
cls = model_instance.__class__
21-
return {
22-
name for name, member in inspect.getmembers(cls) if isinstance(member, property)
23-
}
18+
return {name for name, member in inspect.getmembers(cls) if isinstance(member, property)}
2419

2520

2621
@functools.lru_cache
27-
def get_serializable_keys(model_instance) -> t.Set[str]:
28-
"""
29-
:return: set of keys available for serialization
22+
def get_serializable_keys(model_instance) -> set[str]:
23+
""":return: set of keys available for serialization
3024
:raise: sqlalchemy.exc.NoInspectionAvailable if model_instance is not an sqlalchemy mapper
3125
"""
3226
if model_instance.serializable_keys:

sqlalchemy_serializer/lib/schema.py

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,11 @@
11
import logging
22
from collections import defaultdict
3-
import typing as t
4-
53

64
logger = logging.getLogger("serializer")
75

86

97
class Tree(defaultdict):
10-
def __init__(
11-
self, to_include=None, to_exclude=None, is_greedy=True, *args, **kwargs
12-
):
8+
def __init__(self, to_include=None, to_exclude=None, is_greedy=True, *args, **kwargs):
139
super(Tree, self).__init__(*args, **kwargs)
1410
self.default_factory = Tree
1511
self.to_include = to_include
@@ -63,7 +59,7 @@ def __repr__(self):
6359

6460

6561
class Schema:
66-
def __init__(self, tree: t.Optional[Tree] = None):
62+
def __init__(self, tree: Tree | None = None):
6763
self._tree = tree or Tree()
6864

6965
@property
@@ -112,9 +108,7 @@ def apply(self, rules, is_greedy):
112108

113109
if is_last_key:
114110
if not parent.is_greedy:
115-
logger.debug(
116-
"Ignore rule:%s parent does not accept new rules", raw
117-
)
111+
logger.debug("Ignore rule:%s parent does not accept new rules", raw)
118112
elif rule.is_negative and node.to_include:
119113
logger.debug("Ignore rule:%s leaf includes key:%s", raw, k)
120114
else:
@@ -132,8 +126,7 @@ def is_included(self, key: str) -> bool:
132126
if not node:
133127
return node is None or not node.to_exclude
134128
return True
135-
else:
136-
return bool(node is not None and node.to_include)
129+
return bool(node is not None and node.to_include)
137130

138131
def fork(self, key: str) -> "Schema":
139132
return Schema(tree=self._tree[key])

0 commit comments

Comments
 (0)