mirror of
https://github.com/enpaul/peewee-plus.git
synced 2024-11-22 06:26:54 +00:00
Add PathField class for storing pathlib objects
Add tests for pathfield class Add database fixture for generating test databases
This commit is contained in:
parent
5499d282eb
commit
adab90736c
@ -1,6 +1,74 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
import peewee
|
||||||
|
|
||||||
__title__ = "peewee-plus"
|
__title__ = "peewee-plus"
|
||||||
__version__ = "0.1.0"
|
__version__ = "0.1.0"
|
||||||
__license__ = "MIT"
|
__license__ = "MIT"
|
||||||
__summary__ = "Various extensions, helpers, and utilities for Peewee"
|
__summary__ = "Various extensions, helpers, and utilities for Peewee"
|
||||||
__url__ = "https://github.com/enpaul/peewee-plus/"
|
__url__ = "https://github.com/enpaul/peewee-plus/"
|
||||||
__authors__ = ["Ethan Paul <24588726+enpaul@users.noreply.github.com>"]
|
__authors__ = ["Ethan Paul <24588726+enpaul@users.noreply.github.com>"]
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ["PathField"]
|
||||||
|
|
||||||
|
|
||||||
|
class PathField(peewee.CharField):
|
||||||
|
"""Field class for storing file paths
|
||||||
|
|
||||||
|
This field can be used to simply store pathlib paths in the database without needing to
|
||||||
|
cast to ``str`` on write and ``Path`` on read.
|
||||||
|
|
||||||
|
It can also serve to save paths relative to a root path defined at runtime. This can be
|
||||||
|
useful when an application stores files under a directory defined in the app configuration,
|
||||||
|
such as in an environment variable or a config file.
|
||||||
|
|
||||||
|
For example, if a model is defined like below to load a path from the ``MYAPP_DATA_DIR``
|
||||||
|
environment variable:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
class MyModel(peewee.Model):
|
||||||
|
some_path = peewee_plus.PathField(relative_to=Path(os.environ["MYAPP_DATA_DIR"]))
|
||||||
|
|
||||||
|
|
||||||
|
p1 = MyModel(some_path=Path(os.environ["MYAPP_DATA_DIR"]) / "foo.json").save()
|
||||||
|
p2 = MyModel(some_path=Path("bar.json")).save()
|
||||||
|
|
||||||
|
Then the data directory can be changed without updating the database, and the code can
|
||||||
|
still rely on the database always returning absolute paths:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
>>> os.environ["MYAPP_DATA_DIR"] = "/etc/myapp"
|
||||||
|
>>> [item.some_path for item in MyModel.select()]
|
||||||
|
[PosixPath('/etc/myapp/foo.json'), PosixPath('/etc/myapp/bar.json')]
|
||||||
|
>>>
|
||||||
|
>>> os.environ["MYAPP_DATA_DIR"] = "/opt/myapp/data"
|
||||||
|
>>> [item.some_path for item in MyModel.select()]
|
||||||
|
[PosixPath('/opt/myapp/data/foo.json'), PosixPath('/opt/myapp/data/bar.json')]
|
||||||
|
>>>
|
||||||
|
|
||||||
|
:param relative_to: Optional root path that paths should be stored relative to. If specified
|
||||||
|
then values being set will be converted to relative paths under this path,
|
||||||
|
and values being read will always be absolute paths under this path.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, *args, relative_to: Optional[Path] = None, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.relative_to = relative_to
|
||||||
|
|
||||||
|
def db_value(self, value: Path) -> str:
|
||||||
|
"""Serialize a :class:`pathlib.Path` to a database string"""
|
||||||
|
if value.is_absolute() and self.relative_to:
|
||||||
|
value = value.relative_to(self.relative_to)
|
||||||
|
return super().db_value(value)
|
||||||
|
|
||||||
|
def python_value(self, value: str) -> Path:
|
||||||
|
"""Serialize a database string to a :class:`pathlib.path` object"""
|
||||||
|
return (
|
||||||
|
self.relative_to / Path(super().python_value(value))
|
||||||
|
if self.relative_to
|
||||||
|
else Path(super().python_value(value))
|
||||||
|
)
|
||||||
|
22
tests/fixtures.py
Normal file
22
tests/fixtures.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import uuid
|
||||||
|
|
||||||
|
import peewee
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="function")
|
||||||
|
def fakedb(tmp_path):
|
||||||
|
"""Create a temporary pho-database (fakedb) for testing fields"""
|
||||||
|
|
||||||
|
sqlite = peewee.SqliteDatabase(
|
||||||
|
tmp_path / f"{uuid.uuid4()}.db",
|
||||||
|
pragmas={
|
||||||
|
"journal_mode": "wal",
|
||||||
|
"cache_size": -1 * 64000,
|
||||||
|
"foreign_keys": 1,
|
||||||
|
"ignore_check_constraints": 0,
|
||||||
|
"synchronous": 0,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
yield sqlite
|
70
tests/test_pathfield.py
Normal file
70
tests/test_pathfield.py
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
# 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 peewee_plus
|
||||||
|
from .fixtures import fakedb
|
||||||
|
|
||||||
|
|
||||||
|
def test_conversion(fakedb):
|
||||||
|
"""Test basic usage of PathField for roundtrip compatibility"""
|
||||||
|
|
||||||
|
class TestModel(peewee.Model):
|
||||||
|
class Meta:
|
||||||
|
database = fakedb
|
||||||
|
|
||||||
|
name = peewee.CharField()
|
||||||
|
some_path = peewee_plus.PathField()
|
||||||
|
|
||||||
|
fakedb.create_tables([TestModel])
|
||||||
|
|
||||||
|
path1 = Path("foo", "bar", "baz")
|
||||||
|
model1 = TestModel(name="one", some_path=path1)
|
||||||
|
model1.save()
|
||||||
|
|
||||||
|
model1 = TestModel.get(TestModel.name == "one")
|
||||||
|
assert model1.some_path == path1
|
||||||
|
assert not model1.some_path.is_absolute()
|
||||||
|
|
||||||
|
path2 = Path("/etc", "fizz", "buzz")
|
||||||
|
model2 = TestModel(name="two", some_path=path2)
|
||||||
|
model2.save()
|
||||||
|
|
||||||
|
model2 = TestModel.get(TestModel.name == "two")
|
||||||
|
assert model2.some_path == path2
|
||||||
|
assert model2.some_path.is_absolute()
|
||||||
|
|
||||||
|
|
||||||
|
def test_relative_to(fakedb):
|
||||||
|
"""Test usage of the ``relative_to`` parameter"""
|
||||||
|
|
||||||
|
base_path = Path("/etc", "foobar")
|
||||||
|
|
||||||
|
class TestModel(peewee.Model):
|
||||||
|
class Meta:
|
||||||
|
database = fakedb
|
||||||
|
|
||||||
|
name = peewee.CharField()
|
||||||
|
some_path = peewee_plus.PathField(relative_to=base_path)
|
||||||
|
|
||||||
|
fakedb.create_tables([TestModel])
|
||||||
|
|
||||||
|
path1 = Path("foo", "bar", "baz")
|
||||||
|
model1 = TestModel(name="one", some_path=path1)
|
||||||
|
model1.save()
|
||||||
|
|
||||||
|
model1 = TestModel.get(TestModel.name == "one")
|
||||||
|
assert model1.some_path.is_absolute()
|
||||||
|
assert model1.some_path == base_path / path1
|
||||||
|
|
||||||
|
path2 = Path("fizz", "buzz")
|
||||||
|
model2 = TestModel(name="two", some_path=base_path / path2)
|
||||||
|
model2.save()
|
||||||
|
|
||||||
|
model2 = TestModel.get(TestModel.name == "two")
|
||||||
|
assert model2.some_path.is_absolute()
|
||||||
|
assert model2.some_path == base_path / path2
|
Loading…
Reference in New Issue
Block a user