14 Commits

Author SHA1 Message Date
b11519194d Add test dependencies to support testing on python 3.12 2023-10-13 16:19:44 -04:00
f6e3870067 Bump version to 1.3.1 2023-10-13 14:29:52 -04:00
e96999ed43 Add tests and classifiers for python 3.12
Update transient deps
2023-10-13 14:28:42 -04:00
42798178ae Update changelog for version 1.3.0 2023-05-04 15:26:48 -04:00
d240306a70 Update json field tests to account for error cases 2023-05-04 15:26:27 -04:00
7944d4de48 Fix JSON field raising a db integrity error for app side data 2023-05-04 15:26:27 -04:00
d33dae2774 Add docs for timedelta field 2023-05-04 15:05:27 -04:00
19b149c5c2 Remove source code links from readme to support cross platform pubs
These links would change whenever there was a software update and never
worked on PyPI, so it's best to just remove them
2023-05-04 15:05:27 -04:00
5f81f7cef7 Update mdformat to mitigate CVE-2023-26302 2023-05-04 15:05:27 -04:00
480e3f3875 Clean up the tests
Add tests for __all__ public api definition
Rename test modules for consistency
2023-05-04 15:05:27 -04:00
0d1c642871 Add check for __all__ items 2023-05-04 15:05:27 -04:00
68120fa65a Bump version to 1.3 2023-05-04 15:05:27 -04:00
992df5b478 Add tests for timedelta field 2023-05-04 15:05:27 -04:00
027da380a5 Add timedelta field for storing timedelta objects 2023-05-04 15:05:27 -04:00
16 changed files with 991 additions and 841 deletions

View File

@@ -24,6 +24,8 @@ jobs:
toxenv: py310
- version: "3.11"
toxenv: py311
- version: "3.12"
toxenv: py312
fail-fast: true
steps:
- name: Checkout

View File

@@ -2,6 +2,16 @@
See also: [Github Release Page](https://github.com/enpaul/peewee-plus/releases).
## Version 1.3.0
View this release on: [Github](https://github.com/enpaul/peewee-plus/releases/tag/1.3.0),
[PyPI](https://pypi.org/project/peewee-plus/1.3.0/)
- Add field for storing Datetime Timedeltas
- **BREAKING CHANGE**: Update `JSONField` to raise `ValueError` instead of
`IntegrityError` on bad data write
- Update tests to account for more cases and test public API validity
## Version 1.2.1
View this release on: [Github](https://github.com/enpaul/peewee-plus/releases/tag/1.2.1),

View File

@@ -27,9 +27,10 @@ Examples of unacceptable behavior include:
- The use of sexualized language or imagery, and sexual attention or advances of any kind
- Trolling, insulting or derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or email address, without their
explicit permission
- Other conduct which could reasonably be considered inappropriate in a professional setting
- Publishing others' private information, such as a physical or email address, without
their explicit permission
- Other conduct which could reasonably be considered inappropriate in a professional
setting
## Enforcement Responsibilities
@@ -52,8 +53,8 @@ offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the
community leaders responsible for enforcement at \[INSERT CONTACT METHOD\]. All
complaints will be reviewed and investigated promptly and fairly.
community leaders responsible for enforcement at \[INSERT CONTACT METHOD\]. All complaints
will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the reporter of
any incident.
@@ -105,8 +106,8 @@ toward or disparagement of classes of individuals.
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0,
available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
Community Impact Guidelines were inspired by
[Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at

View File

@@ -14,7 +14,8 @@ release history.
## Documentation
*The documentation for this project is currently a work in progress. Please see the source code for complete docs*
*The documentation for this project is currently a work in progress. Please see the source
code for complete docs*
- [Installing](#installing)
- [Features](#features)
@@ -57,34 +58,36 @@ when using SQLite
### Functions
[`calc_batch_size`](blob/devel/peewee_plus.py#L93) - Helper function for writing
backend-agnostic batch queries while accounting for the
`calc_batch_size` - Helper function for writing backend-agnostic batch queries while
accounting for the
[SQLite max variable limit](https://www.sqlite.org/limits.html#max_variable_number).
[`flat_transaction`](blob/devel/peewee_plus.py#L159) - Decorator function for wrapping
callables in a database transaction without creating nested transactions
`flat_transaction` - Decorator function for wrapping callables in a database transaction
without creating nested transactions
### Classes
[`PathField`](blob/devel/peewee_plus.py#204) - A Peewee database field for storing
`PathField` - A Peewee database field for storing
[Pathlib](https://docs.python.org/3/library/pathlib.html) objects, optionally relative to
a runtime value.
[`PrecisionFloatField`](blob/devel/peewee_plus.py#L262) - A Peewee database field for
storing floats while specifying the
`PrecisionFloatField` - A Peewee database field for storing floats while specifying the
[MySQL precision parameters](https://dev.mysql.com/doc/refman/8.0/en/floating-point-types.html)
`M` and `D`
[`JSONField`](blob/devel/peewee_plus.py#L293) - A Peewee database field for storing
arbitrary JSON-serializable data
`JSONField` - A Peewee database field for storing arbitrary JSON-serializable data
[`EnumField`](blob/devel/peewee_plus.py#L348) - A Peewee database field for storing Enums
by name
`EnumField` - A Peewee database field for storing Enums by name
`TimedeltaField` A Peewee database field for natively storing
[`datetime.timedelta`](https://docs.python.org/3/library/datetime.html#datetime.timedelta)
objects
## For Developers
All project contributors and participants are expected to adhere to the
[Contributor Covenant Code of Conduct, v2](CODE_OF_CONDUCT.md) ([external link](https://www.contributor-covenant.org/version/2/0/code_of_conduct/)).
[Contributor Covenant Code of Conduct, v2](CODE_OF_CONDUCT.md)
([external link](https://www.contributor-covenant.org/version/2/0/code_of_conduct/)).
The `devel` branch has the latest (and potentially unstable) changes. The stable releases
are tracked on [Github](https://github.com/enpaul/peewee-plus/releases),

View File

@@ -17,6 +17,7 @@ Various extensions, helpers, and utilities for `Peewee`_
.. _`Peewee documentation`: https://docs.peewee-orm.com/en/latest/peewee/database.html#recommended-settings
"""
import contextlib
import datetime
import enum
import functools
import json
@@ -32,7 +33,7 @@ import peewee
__title__ = "peewee-plus"
__version__ = "1.2.1"
__version__ = "1.3.1"
__license__ = "MIT"
__summary__ = "Various extensions, helpers, and utilities for Peewee"
__url__ = "https://github.com/enpaul/peewee-plus/"
@@ -54,6 +55,7 @@ __all__ = [
"PrecisionFloatField",
"SQLITE_DEFAULT_PRAGMAS",
"SQLITE_DEFAULT_VARIABLE_LIMIT",
"TimedeltaField",
]
@@ -315,6 +317,8 @@ class JSONField(peewee.TextField): # pylint: disable=abstract-method
:param dump_params: Additional keyword arguments to unpack into :func:`json.dump`
:param load_params: Additional keyword arguments to unpack into :func:`json.load`
:raises ValueError: When attempting to set a non-JSON serializable object to the field
:raises peewee.IntegrityError: When the underlying database value is not JSON serializable
"""
def __init__(
@@ -332,7 +336,7 @@ class JSONField(peewee.TextField): # pylint: disable=abstract-method
try:
return super().db_value(json.dumps(value, **self.dump_params))
except TypeError as err:
raise peewee.IntegrityError(
raise ValueError(
f"Failed to JSON encode object of type '{type(value)}'"
) from err
@@ -399,3 +403,22 @@ class EnumField(peewee.CharField): # pylint: disable=abstract-method
raise peewee.IntegrityError(
f"Enum {self.enumeration.__name__} has no value with name '{value}'"
) from None
class TimedeltaField(peewee.BigIntegerField):
"""Field class for storing python-native Timedelta objects
This is really just a helper wrapper around an integer field that performs the second conversions
automatically. It is a helpful helper though, so it's included.
.. note:: To avoid issues with float precision, this field stores the database value as an integer.
However, this necessitates the usage of the BigInt type to avoid overflowing the value.
Essentially, the value this field ends up storing is the number of microseconds in the
timedelta.
"""
def db_value(self, value: datetime.timedelta) -> int:
return super().db_value(int(value.total_seconds() * 1000000))
def python_value(self, value: int) -> datetime.timedelta:
return datetime.timedelta(seconds=super().python_value(value) / 1000000)

1551
poetry.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
[tool.poetry]
name = "peewee-plus"
version = "1.2.1"
version = "1.3.1"
description = "Various extensions, helpers, and utilities for Peewee"
authors = ["Ethan Paul <24588726+enpaul@users.noreply.github.com>"]
repository = "https://github.com/enpaul/peewee-plus/"
@@ -22,6 +22,7 @@ classifiers = [
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: Implementation :: CPython",
"Topic :: Database",
"Typing :: Typed"
@@ -41,6 +42,8 @@ ruamel-yaml = {version = "^0.17.21", python = "^3.10"}
#
# https://github.com/python-poetry/poetry-plugin-export/issues/176
virtualenv = ">=20.15,<20.16"
setuptools = {version = "^68.2.2", python = "^3.12"}
pip = {version = "^23.2.1", python = "^3.12"}
[tool.poetry.group.ci.dependencies]
poetry = "^1.4.2"
@@ -55,8 +58,8 @@ safety = {version = "^2.2.0", python = "^3.10"}
[tool.poetry.group.static.dependencies]
black = {version = "^22.8.0", python = "^3.10"}
blacken-docs = {version = "^1.12.0", python = "^3.10"}
mdformat = {version = "^0.6.4", python = "^3.10"}
mdformat-gfm = {version = "^0.2", python = "^3.10"}
mdformat = {version = "^0.7.16", python = "^3.10"}
mdformat-gfm = {version = "^0.3.5", python = "^3.10"}
mypy = {version = "^1.2.0", python = "^3.10"}
pre-commit = {version = "^2.15.0", python = "^3.10"}
pre-commit-hooks = {version = "^4.0.1", python = "^3.10"}

View File

@@ -8,6 +8,15 @@ import peewee_plus
from .fixtures import fakedb
def test_public_api():
"""Test that the public API components are exposed via ``__all__``"""
assert peewee_plus.calc_batch_size.__name__ in peewee_plus.__all__
assert peewee_plus.flat_transaction.__name__ in peewee_plus.__all__
assert "SQLITE_DEFAULT_VARIABLE_LIMIT" in peewee_plus.__all__
assert "SQLITE_DEFAULT_PRAGMAS" in peewee_plus.__all__
def test_sqlite(fakedb):
"""Test the calculation of batch sizes on SQLite"""

View File

@@ -11,6 +11,12 @@ import peewee_plus
from .fixtures import fakedb
def test_public_api():
"""Test that the public API components are exposed via ``__all__``"""
assert peewee_plus.EnumField.__name__ in peewee_plus.__all__
def test_enum(fakedb):
"""Test basic functionality of the enum field"""

78
tests/test_json_field.py Normal file
View File

@@ -0,0 +1,78 @@
# pylint: disable=redefined-outer-name
# pylint: disable=missing-class-docstring
# pylint: disable=too-few-public-methods
# pylint: disable=unused-import
from pathlib import Path
import peewee
import pytest
import peewee_plus
from .fixtures import fakedb
def test_public_api():
"""Test that the public API components are exposed via ``__all__``"""
assert peewee_plus.JSONField.__name__ in peewee_plus.__all__
def test_json(fakedb):
"""Test basic usage of JSONField class"""
class TestModel(peewee.Model):
class Meta:
database = fakedb
some_data = peewee_plus.JSONField()
fakedb.create_tables([TestModel])
data = {"foo": 10, "bar": ["hello", "world"], "baz": True}
model = TestModel(some_data=data)
model.save()
model = TestModel.get()
assert model.some_data == data
def test_errors(fakedb):
"""Test that errors are raised as expected"""
class GoodModel(peewee.Model):
class Meta:
database = fakedb
# Needs to match the table name below
table_name = "one_table"
id = peewee.AutoField()
some_data = peewee_plus.JSONField()
class BadModel(peewee.Model):
class Meta:
database = fakedb
# Needs to match the table name above
table_name = "one_table"
id = peewee.AutoField()
some_data = peewee.TextField()
fakedb.create_tables([GoodModel])
with pytest.raises(ValueError):
# The usage of path here is arbitrary, it just needs to be any
# non-JSON serializable type
bad = GoodModel(some_data=Path("."))
bad.save()
good = GoodModel(some_data={"foo": 123})
good.save()
# This is overwriting the ``some_data`` on the above object with garbage
bad = BadModel.get(good.id)
bad.some_data = "This{ string' is not, valid JSON;"
bad.save()
with pytest.raises(peewee.IntegrityError):
GoodModel.get(good.id)

View File

@@ -1,35 +0,0 @@
# pylint: disable=redefined-outer-name
# pylint: disable=missing-class-docstring
# pylint: disable=too-few-public-methods
# pylint: disable=unused-import
from pathlib import Path
import peewee
import pytest
import peewee_plus
from .fixtures import fakedb
def test_json(fakedb):
"""Test basic usage of JSONField class"""
class TestModel(peewee.Model):
class Meta:
database = fakedb
some_data = peewee_plus.JSONField()
fakedb.create_tables([TestModel])
data = {"foo": 10, "bar": ["hello", "world"], "baz": True}
model = TestModel(some_data=data)
model.save()
model = TestModel.get()
assert model.some_data == data
with pytest.raises(peewee.IntegrityError):
bad = TestModel(some_data=Path("."))
bad.save()

View File

@@ -31,3 +31,10 @@ def test_about():
)
is True
)
def test_all():
"""Test that the string entries in ``__all__`` are correct"""
for item in peewee_plus.__all__:
assert hasattr(peewee_plus, item)

View File

@@ -10,6 +10,12 @@ import peewee_plus
from .fixtures import fakedb
def test_public_api():
"""Test that the public API components are exposed via ``__all__``"""
assert peewee_plus.PathField.__name__ in peewee_plus.__all__
def test_conversion(fakedb):
"""Test basic usage of PathField for roundtrip compatibility"""

View File

@@ -8,6 +8,12 @@ import peewee_plus
from .fixtures import fakedb
def test_public_api():
"""Test that the public API components are exposed via ``__all__``"""
assert peewee_plus.PrecisionFloatField.__name__ in peewee_plus.__all__
# There isn't anything we can really test here since this field implements
# a MySQL-specific syntax and we test with SQLite. This test is here just
# to ensure that the behavior is consistent with the normal FloatField when

View File

@@ -0,0 +1,37 @@
# pylint: disable=redefined-outer-name
# pylint: disable=missing-class-docstring
# pylint: disable=too-few-public-methods
# pylint: disable=unused-import
import datetime
from pathlib import Path
import peewee
import peewee_plus
from .fixtures import fakedb
def test_public_api():
"""Test that the public API components are exposed via ``__all__``"""
assert peewee_plus.TimedeltaField.__name__ in peewee_plus.__all__
def test_conversion(fakedb):
"""Test basic usage of PathField for roundtrip compatibility"""
class TestModel(peewee.Model):
class Meta:
database = fakedb
name = peewee.CharField()
some_timedelta = peewee_plus.TimedeltaField()
fakedb.create_tables([TestModel])
delta = datetime.timedelta(seconds=300)
model = TestModel(name="one", some_timedelta=delta)
model.save()
new = TestModel.get(TestModel.name == "one")
assert new.some_timedelta == delta

View File

@@ -1,6 +1,6 @@
[tox]
envlist =
py{37,38,39,310,311}
py{37,38,39,310,311,312}
static
static-tests
security
@@ -19,6 +19,9 @@ commands =
--cov-config {toxinidir}/.coveragerc \
--cov-report term-missing
[testenv:.package]
basepython = python3.10
[testenv:static]
description = Static formatting and quality enforcement
basepython = python3.10