Compare commits

...

41 Commits
1.0.0 ... devel

Author SHA1 Message Date
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
0693d19f7a
Update changelog for version 1.2.1 2023-04-13 15:01:13 -04:00
2ddf57a73f
Bump version to 1.2.1 2023-04-13 15:00:02 -04:00
1fe2eff40a Update mypy to latest version 2023-04-13 14:59:28 -04:00
5df974faa5 Update tests for calc batch size to accomodate new sqlite logic 2023-04-13 14:59:28 -04:00
0d1a142e43 Update docs to point to new line numbers
There's gotta be a better way to do this
2023-04-13 14:59:28 -04:00
5e68ee3055 Add logic for using updated sqlite3 default var limit
See here for details: https://www.sqlite.org/limits.html#max_variable_number
2023-04-13 14:59:28 -04:00
f9d1f9ecd2 Pin virtualenv to bypass python-poetry/poetry-plugin-export#176 2023-04-13 14:41:13 -04:00
d5899d61c7 Rename CI check stage to just 'check' 2023-04-13 14:41:13 -04:00
750d3c07b6 Update CI and toxfile to use new group structure 2023-04-13 14:41:13 -04:00
ac342d4dc6 Update pyproject to use dep groups 2023-04-13 14:41:13 -04:00
6e3c9e8139 Remove deprecated option from pylintrc 2023-04-13 14:41:13 -04:00
23b6359d86 Add python 3.11 pypi classifier
Add python3.11 to tox CI
2023-04-13 14:41:13 -04:00
7ecd592ae0 Update developer docs to specify poetry 1.2 and python 3.10 2023-04-13 14:41:13 -04:00
6f97ff74e0 Fix documentation errors
Add more links to external docs where appropriate
Fix readme links hardcoded to github and 1.0.0 tag
Clarify intended usage of the calc_batch_size function
2023-04-13 14:41:13 -04:00
5520faef88 Update changelog for version 1.2.0 2022-11-15 10:16:24 -05:00
60bdfbfb17 Remove support for python 3.6
Update transient dependencies
Update actions workflow to install poetry 1.2
Update makefile to use poetry 1.2 command structure
Add safety exclusions to account for outdated meta tools
Bump feature version to 1.2
2022-11-15 10:16:24 -05:00
d5d400e0a6 Update CI to run non-platform checks on python-3.10 2022-11-15 10:16:24 -05:00
e98aa73a8d Update changelog with version 1.1.1 2022-11-15 10:16:24 -05:00
4f5e007d7d Add badge for downloads per month 2022-11-15 10:16:24 -05:00
134c28fd92 Bump patch version 2022-11-15 10:16:24 -05:00
Evgeny Chernyavskiy
dac554e8d4 Ensure NULL-able Enums don't raise peewee.IntegrityError 2022-09-07 15:34:45 -04:00
44ad5da339 Update name of check CI workflow to improve clarity 2022-09-07 15:04:12 -04:00
ef8800059f Update black to stable release
Update transient dependencies to fix security errors
2022-09-07 15:04:12 -04:00
c6cab6ce2d Add build status badge to readme 2022-08-05 00:05:34 -04:00
6e9c3c2521 Update transient dependencies 2022-08-05 00:05:34 -04:00
02642312fb Add github actions CI 2022-08-05 00:05:34 -04:00
8076d8cd29
Update docs for new flat_transaction function 2022-01-20 01:05:25 -05:00
d2f66ce40e
Update changelog with version 1.1 2022-01-20 01:03:12 -05:00
d33ffd3d8c
Bump feature version 2022-01-20 01:01:48 -05:00
b8a684cec7
Add flat transaction decorator function 2022-01-20 00:59:02 -05:00
19 changed files with 2517 additions and 1462 deletions

35
.github/scripts/setup-env.sh vendored Executable file
View File

@ -0,0 +1,35 @@
#!/usr/bin/env bash
#
# Environment setup script for the local project. Intended to be used with automation
# to create a repeatable local environment for tests to be run in. The python env
# this script creates can be accessed at the location defined by the CI_VENV variable
# below.
set -e;
CI_CACHE=$HOME/.cache;
INSTALL_POETRY_VERSION="${POETRY_VERSION:-1.4.1}";
mkdir --parents "$CI_CACHE";
command -v python;
python --version;
curl --location https://install.python-poetry.org \
--output "$CI_CACHE/install-poetry.py" \
--silent \
--show-error;
python "$CI_CACHE/install-poetry.py" \
--version "$POETRY_VERSION" \
--yes;
poetry --version --no-ansi;
poetry run pip --version;
poetry install \
--sync \
--no-ansi \
--no-root \
--only ci;
poetry env info;
poetry run tox --version;

92
.github/workflows/ci.yaml vendored Normal file
View File

@ -0,0 +1,92 @@
---
name: CI
on:
pull_request:
types: ["opened", "synchronize"]
push:
branches: ["devel"]
env:
POETRY_VERSION: 1.4.1
jobs:
Test:
name: Python ${{ matrix.python.version }}
runs-on: ubuntu-latest
strategy:
matrix:
python:
- version: "3.7"
toxenv: py37
- version: "3.8"
toxenv: py38
- version: "3.9"
toxenv: py39
- version: "3.10"
toxenv: py310
- version: "3.11"
toxenv: py311
fail-fast: true
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Install Python ${{ matrix.python.version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python.version }}
- name: Configure Job Cache
uses: actions/cache@v3
with:
path: |
~/.cache/pip
~/.cache/pypoetry/cache
~/.poetry
# Including the hashed poetry.lock in the cache slug ensures that the cache
# will be invalidated, and thus all packages will be redownloaded, if the
# lockfile is updated
key: ${{ runner.os }}-${{ matrix.python.toxenv }}-${{ hashFiles('**/poetry.lock') }}
- name: Configure Path
run: echo "$HOME/.local/bin" >> $GITHUB_PATH
- name: Configure Environment
run: .github/scripts/setup-env.sh
- name: Run Toxenv ${{ matrix.python.toxenv }}
run: poetry run tox -e ${{ matrix.python.toxenv }}
Check:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Install Python 3.10
uses: actions/setup-python@v4
with:
python-version: "3.10"
- name: Configure Job Cache
uses: actions/cache@v3
with:
path: |
~/.cache/pip
~/.cache/pypoetry/cache
~/.poetry
# Hardcoded 'py310' slug here lets this cache piggyback on the 'py310' cache
# that is generated for the tests above
key: ${{ runner.os }}-py310-${{ hashFiles('**/poetry.lock') }}
- name: Configure Path
run: echo "$HOME/.local/bin" >> $GITHUB_PATH
- name: Configure Environment
run: .github/scripts/setup-env.sh
- name: Run Static Analysis Checks
run: poetry run tox -e static
- name: Run Static Analysis Checks (Tests)
run: poetry run tox -e static-tests
- name: Run Security Checks
run: poetry run tox -e security

View File

@ -11,7 +11,6 @@
# --disable=W"
disable=logging-fstring-interpolation
,logging-format-interpolation
,bad-continuation
,line-too-long
,ungrouped-imports
,typecheck

View File

@ -2,6 +2,40 @@
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),
[PyPI](https://pypi.org/project/peewee-plus/1.2.1/)
- Add PyPI classifier for Python 3.11
- Fix SQLite variable limit determination to account for changes in SQLite 3.32
## Version 1.2.0
View this release on: [Github](https://github.com/enpaul/peewee-plus/releases/tag/1.2.0),
[PyPI](https://pypi.org/project/peewee-plus/1.2.0/)
- Remove support for Python 3.6
- Update development workflows to use Poetry 1.2
- Fix nullable enums raising an `IntegrityError` when value is `None` (#1)
## Version 1.1.0
View this release on: [Github](https://github.com/enpaul/peewee-plus/releases/tag/1.1.0),
[PyPI](https://pypi.org/project/peewee-plus/1.1.0/)
- Add decorator function for wrapping callables in a flat database transaction
## Version 1.0.0
View this release on: [Github](https://github.com/enpaul/peewee-plus/releases/tag/1.0.0),

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

@ -33,11 +33,11 @@ source: ## Build Python source distribution package
build: clean wheel source; ## Build all distribution packages
test: clean-tox ## Run the project testsuite(s)
poetry run tox
poetry run tox --parallel
publish: clean test build ## Build and upload to pypi (requires $PYPI_API_KEY be set)
@poetry publish --username __token__ --password $(PYPI_API_KEY)
dev: ## Create local dev environment
poetry install --remove-untracked
poetry install --sync --with dev --with ci --with test --with security --with static
poetry run pre-commit install

View File

@ -2,7 +2,9 @@
Various extensions, helpers, and utilities for [Peewee](http://peewee-orm.com)
[![CI Status](https://github.com/enpaul/peewee-plus/workflows/CI/badge.svg?event=push)](https://github.com/enpaul/peewee-plus/actions)
[![PyPI Version](https://img.shields.io/pypi/v/peewee-plus)](https://pypi.org/project/peewee-plus/)
[![PyPI Downloads](https://img.shields.io/pypi/dm/peewee-plus)](https://libraries.io/pypi/peewee-plus)
[![License](https://img.shields.io/pypi/l/peewee-plus)](https://opensource.org/licenses/MIT)
[![Python Supported Versions](https://img.shields.io/pypi/pyversions/peewee-plus)](https://www.python.org)
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
@ -12,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)
@ -20,7 +23,7 @@ release history.
## Installing
Peewee+ is [available on PyPI](https://pypi.org/project/peewee-plus/) and can be installed
peewee+ is [available on PyPI](https://pypi.org/project/peewee-plus/) and can be installed
using Poetry, Pipenv, or Pip:
```bash
@ -55,31 +58,36 @@ when using SQLite
### Functions
[`calc_batch_size`](https://github.com/enpaul/peewee-plus/blob/1.0.0/peewee_plus.py#L68) -
Helper function for determining how to batch a create/update query with SQLite
`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` - Decorator function for wrapping callables in a database transaction
without creating nested transactions
### Classes
[`PathField`](https://github.com/enpaul/peewee-plus/blob/1.0.0/peewee_plus.py#134) - 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`](https://github.com/enpaul/peewee-plus/blob/1.0.0/peewee_plus.py#L192)
\- 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`](https://github.com/enpaul/peewee-plus/blob/1.0.0/peewee_plus.py#L222) - A
Peewee database field for storing arbitrary JSON-serializable data
`JSONField` - A Peewee database field for storing arbitrary JSON-serializable data
[`EnumField`](https://github.com/enpaul/peewee-plus/blob/1.0.0/peewee_plus.py#L277) - 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),
@ -94,8 +102,8 @@ are tracked on [Github](https://github.com/enpaul/peewee-plus/releases),
[fork the repository](https://docs.github.com/en/enterprise/2.20/user/github/getting-started-with-github/fork-a-repo)
and [open a pull request](https://github.com/enpaul/peewee-plus/compare).
Developing this project requires at least [Python 3.7](https://www.python.org/downloads/)
and at least [Poetry 1.0](https://python-poetry.org/docs/#installation). GNU Make can
Developing this project requires [Python 3.10](https://www.python.org/downloads/) or later
and [Poetry 1.2](https://python-poetry.org/docs/#installation) or later. GNU Make can
optionally be used to quickly setup a local development environment, but this is not
required.

View File

@ -1,4 +1,6 @@
"""Peewee+
"""peewee+
Various extensions, helpers, and utilities for `Peewee`_
:constant SQLITE_DEFAULT_VARIABLE_LIMIT: The default number of variables that a single SQL query
can contain when interfacing with SQLite. The actual
@ -10,9 +12,14 @@
SQLite database connection. The value for this constant is taken
directly from the `Peewee documentation`_
.. _`Peewee documentation`: http://docs.peewee-orm.com/en/latest/peewee/database.html#recommended-settings
.. _`Peewee`: https://docs.peewee-orm.com/en/latest/
.. _`Peewee documentation`: https://docs.peewee-orm.com/en/latest/peewee/database.html#recommended-settings
"""
import contextlib
import datetime
import enum
import functools
import json
from pathlib import Path
from typing import Any
@ -26,7 +33,7 @@ import peewee
__title__ = "peewee-plus"
__version__ = "1.0.0"
__version__ = "1.3.0"
__license__ = "MIT"
__summary__ = "Various extensions, helpers, and utilities for Peewee"
__url__ = "https://github.com/enpaul/peewee-plus/"
@ -42,11 +49,13 @@ __all__ = [
"__authors__",
"calc_batch_size",
"EnumField",
"flat_transaction",
"JSONField",
"PathField",
"PrecisionFloatField",
"SQLITE_DEFAULT_PRAGMAS",
"SQLITE_DEFAULT_VARIABLE_LIMIT",
"TimedeltaField",
]
@ -59,7 +68,25 @@ SQLITE_DEFAULT_PRAGMAS: Dict[str, Any] = {
}
SQLITE_DEFAULT_VARIABLE_LIMIT: int = 999
SQLITE_DEFAULT_VARIABLE_LIMIT: int
# With SQLite 3.32 (2020-05-22) the devs bumped the default variable limit to
# 32766. This logic attemps to import the sqlite3 bindings and determine whether
# the version of the installed SQLite version is greater or equal to 3.32. If
# the sqlite3 bindings cannot be imported (either because they aren't installed)
# or because the platform is using SQLite 1 or 2 then it falls back to the
# 999 value.
try:
import sqlite3
except ImportError:
SQLITE_DEFAULT_VARIABLE_LIMIT = 999
else:
if sqlite3.sqlite_version_info[0] >= 3 or (
sqlite3.sqlite_version_info[0] == 3 and sqlite3.sqlite_version_info[1] >= 32
):
SQLITE_DEFAULT_VARIABLE_LIMIT = 32766
else:
SQLITE_DEFAULT_VARIABLE_LIMIT = 999
T = TypeVar("T", bound=peewee.Model)
@ -131,7 +158,52 @@ def calc_batch_size(
return len(models)
class PathField(peewee.CharField):
def flat_transaction(interface: peewee.Database):
"""Database transaction wrapper that avoids nested transactions
A decorator that can be used to decorate functions or methods so that the entire callable
is executed in a single transaction context. If a transaction is already open then it will
be reused rather than opening a nested transaction.
Example usage:
.. code-block:: python
db = peewee.SqliteDatabase("test.db")
@flat_transaction(db)
def subquery():
...
@flat_transaction(db)
def my_query():
...
subquery()
# This call opens only a single transaction
my_query()
:param interface: Peewee database interface that should be used to open the transaction
"""
def outer(func):
@functools.wraps(func)
def inner(*args, **kwargs):
with interface.atomic() if not interface.in_transaction() else contextlib.nullcontext():
return func(*args, **kwargs)
return inner
return outer
# TODO: The disable=abstract-method pragmas below are to get around new linting warnings
# in pylint>2.12, but they haven't been addressed properly. They should be revisited
# and fixed properly in the future.
class PathField(peewee.CharField): # pylint: disable=abstract-method
"""Field class for storing file paths
This field can be used to simply store pathlib paths in the database without needing to
@ -189,7 +261,7 @@ class PathField(peewee.CharField):
)
class PrecisionFloatField(peewee.FloatField):
class PrecisionFloatField(peewee.FloatField): # pylint: disable=abstract-method
"""Field class for storing floats with custom precision parameters
This field adds support for specifying the ``M`` and ``D`` precision parameters of a
@ -206,8 +278,9 @@ class PrecisionFloatField(peewee.FloatField):
.. _here: https://stackoverflow.com/a/67476045/5361209
:param max_digits: Maximum number of digits, combined from left and right of the decimal place,
to store for the value.
:param decimal_places: Maximum number of digits that will be stored after the decimal place
to store for the value; corresponds to the ``M`` MySQL precision parameter.
:param decimal_places: Maximum number of digits that will be stored after the decimal place;
corresponds to the ``D`` MySQL precision parameter.
"""
def __init__(self, *args, max_digits: int = 10, decimal_places: int = 4, **kwargs):
@ -219,10 +292,10 @@ class PrecisionFloatField(peewee.FloatField):
return [self.max_digits, self.decimal_places]
class JSONField(peewee.TextField):
class JSONField(peewee.TextField): # pylint: disable=abstract-method
"""Field class for storing JSON-serializable data
This field can be used to store a dictionary of data directly in the database without needing
This field can be used to store a dictionary of data directly in the database
without needing to call :func:`json.dumps` and :func:`json.loads` directly.
::
@ -244,6 +317,8 @@ class JSONField(peewee.TextField):
: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__(
@ -261,7 +336,7 @@ class JSONField(peewee.TextField):
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
@ -274,7 +349,7 @@ class JSONField(peewee.TextField):
) from err
class EnumField(peewee.CharField):
class EnumField(peewee.CharField): # pylint: disable=abstract-method
"""Field class for storing Enums
This field can be used for storing members of an :class:`enum.Enum` in the database,
@ -319,8 +394,31 @@ class EnumField(peewee.CharField):
def python_value(self, value: str) -> enum.Enum:
try:
return self.enumeration[super().python_value(value)]
return (
None
if value is None and self.null
else self.enumeration[super().python_value(value)]
)
except KeyError:
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)

3338
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.0.0"
version = "1.3.0"
description = "Various extensions, helpers, and utilities for Peewee"
authors = ["Ethan Paul <24588726+enpaul@users.noreply.github.com>"]
repository = "https://github.com/enpaul/peewee-plus/"
@ -17,40 +17,57 @@ classifiers = [
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: Implementation :: CPython",
"Topic :: Database",
"Typing :: Typed"
]
[tool.poetry.dependencies]
python = "^3.6.1"
python = "^3.7.1"
peewee = "^3.14.8"
[tool.poetry.dev-dependencies]
bandit = "^1.7.1"
black = {version = "^21.11b1", python = "^3.7"}
blacken-docs = {version = "^1.12.0", python = "^3.7"}
ipython = {version = "^7.29.0", python = "^3.7"}
mdformat = "^0.6.4"
mdformat-gfm = "^0.2"
mypy = "^0.910"
pre-commit = "^2.15.0"
pre-commit-hooks = "^4.0.1"
pylint = "^2.11.1"
[tool.poetry.group.test.dependencies]
pytest = "^6.2.5"
pytest-cov = "^3.0.0"
reorder-python-imports = "^2.6.0"
safety = "^1.10.3"
toml = "^0.10.2"
ruamel-yaml = {version = "^0.17.21", python = "^3.10"}
# This is a workaround for this issue with the Poetry export
# plugin which was blocking the 'security' CI check:
#
# https://github.com/python-poetry/poetry-plugin-export/issues/176
virtualenv = ">=20.15,<20.16"
[tool.poetry.group.ci.dependencies]
poetry = "^1.4.2"
tox = "^3.24.4"
tox-poetry-installer = {extras = ["poetry"], version = "^0.8.2"}
types-toml = "^0.10.1"
tox-poetry-installer = {version = "^0.10.0", extras = ["poetry"]}
[tool.poetry.group.security.dependencies]
bandit = {version = "^1.7.1", python = "^3.10"}
poetry = {version = "^1.4.2", python = "^3.10"}
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.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"}
pylint = {version = "^2.13.0", python = "^3.10"}
reorder-python-imports = {version = "^2.6.0", python = "^3.10"}
toml = {version = "^0.10.2", python = "^3.10"}
types-toml = {version = "^0.10.1", python = "^3.10"}
[tool.poetry.group.dev.dependencies]
ipython = {version = "^8.10.0", python = "^3.10"}
[build-system]
requires = ["poetry-core>=1.0.0"]
requires = ["poetry-core>=1.1.0"]
build-backend = "poetry.core.masonry.api"

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"""
@ -17,7 +26,11 @@ def test_sqlite(fakedb):
data = peewee.IntegerField()
models = [TestModel(item) for item in range(500)]
# Three is just chosen as an arbitrary multiplier to ensure the value is larger than the
# sqlite variable limit
models = [
TestModel(item) for item in range(peewee_plus.SQLITE_DEFAULT_VARIABLE_LIMIT * 3)
]
assert (
peewee_plus.calc_batch_size(models) <= peewee_plus.SQLITE_DEFAULT_VARIABLE_LIMIT
)

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

53
tox.ini
View File

@ -1,6 +1,6 @@
[tox]
envlist =
py{36,37,38,39,310}
py{37,38,39,310,311}
static
static-tests
security
@ -11,11 +11,8 @@ skip_missing_interpreters = true
description = Run the tests
require_locked_deps = true
require_poetry = true
locked_deps =
pytest
pytest-cov
ruamel.yaml
toml
poetry_dep_groups =
test
commands =
pytest {toxinidir}/tests/ \
--cov peewee_plus \
@ -24,18 +21,10 @@ commands =
[testenv:static]
description = Static formatting and quality enforcement
basepython = python3.8
basepython = python3.10
ignore_errors = true
locked_deps =
black
blacken-docs
mdformat
mdformat-gfm
mypy
reorder-python-imports
pre-commit
pre-commit-hooks
pylint
poetry_dep_groups =
static
commands =
pre-commit run \
--all-files
@ -47,13 +36,11 @@ commands =
[testenv:static-tests]
description = Static formatting and quality enforcement for the tests
basepython = python3.8
basepython = python3.10
ignore_errors = true
locked_deps =
mypy
pylint
pytest
types-toml
poetry_dep_groups =
static
test
commands =
pylint {toxinidir}/tests/ \
--rcfile {toxinidir}/.pylintrc
@ -63,13 +50,11 @@ commands =
[testenv:security]
description = Security checks
basepython = python3.8
basepython = python3.10
skip_install = true
ignore_errors = true
locked_deps =
bandit
safety
poetry
poetry_dep_groups =
security
commands =
bandit {toxinidir}/peewee_plus.py \
--recursive \
@ -82,7 +67,13 @@ commands =
--format requirements.txt \
--output {envtmpdir}/requirements.txt \
--without-hashes \
--dev
--with ci \
--with test \
--with security \
--with static \
--with dev
safety check \
--json \
--file {envtmpdir}/requirements.txt
--file {envtmpdir}/requirements.txt \
--output text \
# https://github.com/pytest-dev/py/issues/287
--ignore 51457