Skip to content

Commit 155f030

Browse files
committed
chore: Pypi tests and handy make commands
1 parent 50c504d commit 155f030

5 files changed

Lines changed: 128 additions & 7 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
dist
22
env
3+
.env
34

45
.idea
56
__MACOSX

Makefile

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

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

7+
test-pypi:
8+
uv run pytest -xvrs --color=yes -m pypi tests/test_pypi_readiness.py::test_pypi_version_can_be_installed_and_used
9+
710
format:
811
uv run ruff format .
912
uv run ruff check --fix .
13+
14+
publish:
15+
uv build --no-sources
16+
UV_PUBLISH_TOKEN="$(PYPI_TOKEN)" uv publish

pyproject.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,11 @@ build-backend = "setuptools.build_meta"
4545
include-package-data = true
4646

4747
[tool.pytest.ini_options]
48-
addopts = "-xvrs --color=yes"
48+
addopts = "-xvrs --color=yes -m 'not pypi'"
4949
log_cli = true
50+
markers = [
51+
"pypi: marks tests that require PyPI access (deselect with -m 'not pypi')",
52+
]
5053

5154

5255
[tool.ruff]

sqlalchemy_serializer/serializer.py

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@
1717
logger = logging.getLogger("serializer")
1818
logger.setLevel(level="WARN")
1919

20+
SERIALIZER_DEFAULT_DATE_FORMAT = "%Y-%m-%d"
21+
SERIALIZER_DEFAULT_DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S"
22+
SERIALIZER_DEFAULT_TIME_FORMAT = "%H:%M"
23+
SERIALIZER_DEFAULT_DECIMAL_FORMAT = "{}"
24+
2025

2126
class SerializerMixin:
2227
"""Mixin for retrieving public fields of sqlAlchemy-model in json-compatible format
@@ -38,10 +43,10 @@ class SerializerMixin:
3843
# Custom list of fields to serialize in this model
3944
serializable_keys: tuple = ()
4045

41-
date_format = "%Y-%m-%d"
42-
datetime_format = "%Y-%m-%d %H:%M:%S"
43-
time_format = "%H:%M"
44-
decimal_format = "{}"
46+
date_format = SERIALIZER_DEFAULT_DATE_FORMAT
47+
datetime_format = SERIALIZER_DEFAULT_DATETIME_FORMAT
48+
time_format = SERIALIZER_DEFAULT_TIME_FORMAT
49+
decimal_format = SERIALIZER_DEFAULT_DECIMAL_FORMAT
4550

4651
# Serialize fields of the model defined as @property automatically
4752
auto_serialize_properties: bool = False
@@ -111,7 +116,18 @@ class Serializer:
111116

112117
def __init__(self, **kwargs):
113118
self.set_serialization_depth(0)
114-
self.set_options(Options(**kwargs))
119+
# Provide defaults for Options if not specified
120+
options_kwargs = {
121+
"date_format": kwargs.get("date_format", SERIALIZER_DEFAULT_DATE_FORMAT),
122+
"datetime_format": kwargs.get(
123+
"datetime_format", SERIALIZER_DEFAULT_DATETIME_FORMAT
124+
),
125+
"time_format": kwargs.get("time_format", SERIALIZER_DEFAULT_TIME_FORMAT),
126+
"decimal_format": kwargs.get("decimal_format", SERIALIZER_DEFAULT_DECIMAL_FORMAT),
127+
"tzinfo": kwargs.get("tzinfo"),
128+
"serialize_types": kwargs.get("serialize_types", ()),
129+
}
130+
self.set_options(Options(**options_kwargs))
115131
self.init_callbacks()
116132

117133
self.schema = Schema()

tests/test_pypi_readiness.py

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from pathlib import Path
1515

1616
import pytest
17+
import requests
1718

1819
# Handle TOML parsing for different Python versions
1920
try:
@@ -220,3 +221,96 @@ def test_package_can_be_installed():
220221
f"STDOUT: {import_result.stdout}\n"
221222
f"STDERR: {import_result.stderr}"
222223
)
224+
225+
226+
@pytest.mark.pypi
227+
def test_pypi_version_can_be_installed_and_used():
228+
"""Test that the current version from PyPI can be installed and used."""
229+
if tomllib is None:
230+
pytest.skip("tomllib or tomli is required to get package name")
231+
232+
project_root = Path(__file__).parent.parent
233+
pyproject_path = project_root / "pyproject.toml"
234+
235+
# Get package name from pyproject.toml
236+
with open(pyproject_path, "rb") as f:
237+
config = tomllib.load(f)
238+
239+
package_name = config.get("project", {}).get("name")
240+
if not package_name:
241+
pytest.skip("Package name not found in pyproject.toml")
242+
243+
# Query PyPI API for latest version
244+
pypi_url = f"https://pypi.org/pypi/{package_name}/json"
245+
try:
246+
response = requests.get(pypi_url, timeout=10)
247+
response.raise_for_status()
248+
pypi_data = response.json()
249+
except requests.RequestException as e:
250+
pytest.skip(f"Failed to query PyPI: {e}")
251+
252+
# Get the latest version
253+
latest_version = pypi_data.get("info", {}).get("version")
254+
if not latest_version:
255+
pytest.skip("No version found in PyPI response")
256+
257+
# Create a temporary virtual environment
258+
with tempfile.TemporaryDirectory() as tmpdir:
259+
venv_path = Path(tmpdir) / "test_venv"
260+
261+
# Create virtual environment
262+
subprocess.run(
263+
[sys.executable, "-m", "venv", str(venv_path)],
264+
check=True,
265+
capture_output=True,
266+
)
267+
268+
# Get pip path
269+
if sys.platform == "win32":
270+
pip_path = venv_path / "Scripts" / "pip"
271+
python_path = venv_path / "Scripts" / "python"
272+
else:
273+
pip_path = venv_path / "bin" / "pip"
274+
python_path = venv_path / "bin" / "python"
275+
276+
# Install the package from PyPI
277+
install_result = subprocess.run(
278+
[str(pip_path), "install", f"{package_name}=={latest_version}"],
279+
capture_output=True,
280+
text=True,
281+
check=False,
282+
)
283+
284+
assert install_result.returncode == 0, (
285+
f"PyPI package installation failed.\n"
286+
f"STDOUT: {install_result.stdout}\n"
287+
f"STDERR: {install_result.stderr}"
288+
)
289+
290+
# Verify package can be imported
291+
import_test_code = """
292+
import sqlalchemy_serializer
293+
from sqlalchemy_serializer import SerializerMixin, Serializer
294+
295+
# Test basic import
296+
assert hasattr(sqlalchemy_serializer, 'SerializerMixin')
297+
assert hasattr(sqlalchemy_serializer, 'Serializer')
298+
299+
# Test Serializer can be instantiated
300+
serializer = Serializer()
301+
assert serializer is not None
302+
303+
print('OK')
304+
"""
305+
import_result = subprocess.run(
306+
[str(python_path), "-c", import_test_code],
307+
capture_output=True,
308+
text=True,
309+
check=False,
310+
)
311+
312+
assert import_result.returncode == 0, (
313+
f"Package usage test failed after PyPI installation.\n"
314+
f"STDOUT: {import_result.stdout}\n"
315+
f"STDERR: {import_result.stderr}"
316+
)

0 commit comments

Comments
 (0)