mirror of
				https://github.com/enpaul/peewee-plus.git
				synced 2025-11-04 01:08:38 +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:
		@@ -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
 | 
				
			||||||
		Reference in New Issue
	
	Block a user