mirror of
https://github.com/enpaul/peewee-plus.git
synced 2024-12-22 15:53:29 +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"
|
||||
__version__ = "0.1.0"
|
||||
__license__ = "MIT"
|
||||
__summary__ = "Various extensions, helpers, and utilities for Peewee"
|
||||
__url__ = "https://github.com/enpaul/peewee-plus/"
|
||||
__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