-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Closes #25
- Loading branch information
Showing
16 changed files
with
492 additions
and
0 deletions.
There are no files selected for viewing
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
"""The :mod:`anfema_django_testutils.contrib.fixtures` app provides to find fixture and fixture media files from each | ||
of your applications and any other specified places. | ||
Settings | ||
======== | ||
``FIXTURE_FINDERS`` | ||
``FIXTURE_DIRS`` | ||
``FIXTURE_MEDIAFILE_FINDERS`` | ||
``FIXTURE_MEDIAFILE_DIRS`` | ||
Management Commands | ||
=================== | ||
:mod:`anfema_django_testutils.contrib.fixtures` exposes three management commands. | ||
findfixture | ||
----------- | ||
*django-admin findfixture FILE [FILE ...]* | ||
Finds the absolute paths for given fixture file(s). | ||
findfixturemedia | ||
---------------- | ||
*django-admin findfixturemedia FILE [FILE ...]* | ||
Finds the absolute paths for given fixture media file(s). | ||
collectfixturemedia | ||
------------------- | ||
*django-admin collectfixturemedia* | ||
Collect fixture media files in a single location. | ||
""" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import os | ||
|
||
from django.apps import AppConfig | ||
from django.core.checks import register | ||
from django.utils.translation import gettext_lazy as _ | ||
|
||
from .checks import check_config | ||
|
||
|
||
class FixturesConfig(AppConfig): | ||
default_auto_field = "django.db.models.BigAutoField" | ||
name = "anfema_django_testutils.contrib.fixtures" | ||
label = "fixtures" | ||
verbose_name = _("fixtures") | ||
|
||
fixture_source_dir = "fixtures" | ||
fixture_suffixes = [".json", ".yaml", ".yml", ".xml"] | ||
|
||
fixturemedia_source_dir = os.path.join(fixture_source_dir, "media") | ||
|
||
def ready(self): | ||
register(check_config, self.name) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
from pathlib import Path | ||
|
||
from django.core.checks import Error, Warning | ||
|
||
from .settings import get_config | ||
|
||
|
||
def check_config(app_configs=None, **kwargs): | ||
"""Check the configuration settings for correctness.""" | ||
from .apps import FixturesConfig | ||
|
||
errors = [] | ||
|
||
app_label = FixturesConfig.label | ||
config = get_config() | ||
|
||
if not isinstance(config["FIXTURE_FINDERS"], (tuple, list)): | ||
errors.append( | ||
Error( | ||
"The FIXTURE_FINDERS setting must be a list or tuple.", | ||
id=f"{app_label}.E001", | ||
obj="Improper Configuration", | ||
), | ||
) | ||
if not isinstance(config["FIXTURE_DIRS"], (tuple, list)): | ||
errors.append( | ||
Error( | ||
"The FIXTURE_DIRS setting must be a a list or tuple.", | ||
id=f"{app_label}.E001", | ||
obj="Improper Configuration", | ||
), | ||
) | ||
elif not all([isinstance(i, str) for i in config["FIXTURE_DIRS"]]): | ||
errors.append( | ||
Error( | ||
f"At least one item of FIXTURE_DIRS settings is not a string.", | ||
id=f"{app_label}.E001", | ||
obj="Improper Configuration", | ||
), | ||
) | ||
else: | ||
for path in filter(lambda p: not Path(p).exists(), config["FIXTURE_DIRS"]): | ||
errors.append( | ||
Warning( | ||
f"The FIXTURE_DIRS settings contains a non-existing path: {path!r}.", | ||
id=f"{app_label}.W001", | ||
obj="Improper Configuration", | ||
), | ||
) | ||
|
||
if not isinstance(config["FIXTURE_MEDIAFILE_FINDERS"], (tuple, list)): | ||
errors.append( | ||
Error( | ||
"The FIXTURE_MEDIAFILE_FINDERS setting must be a list or tuple.", | ||
id=f"{app_label}.E001", | ||
obj="Improper Configuration", | ||
), | ||
) | ||
|
||
if not isinstance(config["FIXTURE_MEDIAFILE_DIRS"], (tuple, list)): | ||
errors.append( | ||
Error( | ||
"The FIXTURE_MEDIAFILE_DIRS setting must be a a list or tuple.", | ||
id=f"{app_label}.E001", | ||
obj="Improper Configuration", | ||
), | ||
) | ||
elif not all([isinstance(i, str) for i in config["FIXTURE_MEDIAFILE_DIRS"]]): | ||
errors.append( | ||
Error( | ||
f"At least one item of FIXTURE_MEDIAFILE_DIRS settings is not a string.", | ||
id=f"{app_label}.E001", | ||
obj="Improper Configuration", | ||
), | ||
) | ||
else: | ||
for path in filter(lambda p: not Path(p).exists(), config["FIXTURE_MEDIAFILE_DIRS"]): | ||
errors.append( | ||
Warning( | ||
f"The FIXTURE_MEDIAFILE_DIRS settings contains a non-existing path: {path!r}.", | ||
id=f"{app_label}.W001", | ||
obj="Improper Configuration", | ||
), | ||
) | ||
|
||
return errors |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
from . import fixture, media |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
from typing import Generator, Union | ||
|
||
from django.contrib.staticfiles.finders import ( | ||
AppDirectoriesFinder, | ||
BaseFinder, | ||
FileSystemFinder, | ||
FileSystemStorage, | ||
get_finder, | ||
) | ||
|
||
|
||
def find_file(finders, path: str, all: bool = False) -> Union[str, list[str], None]: | ||
"""Find a fixture media file with the given path using all enabled fixture media files finders. | ||
:param str path: Path to search for. | ||
:param bool all: Defines whether to return only the first match or search for all matches. | ||
:return: If ``all`` is ``False`` (default), return the first matching absolute path (or ``None`` | ||
if no match). Otherwise return a list. | ||
""" | ||
matches = [] | ||
for finder in get_finders(finders): | ||
result = finder.find(path, all=all) | ||
if not all and result: | ||
return result | ||
matches.extend([result] if not isinstance(result, (list, tuple)) else result) | ||
return matches or ([] if all else None) | ||
|
||
|
||
def get_finders(finders: list[str]) -> Generator[BaseFinder, None, None]: | ||
"""Yield enabled finder classes listed in *finders*.""" | ||
yield from map(get_finder, finders) | ||
|
||
|
||
class BaseFileSystemFinder(FileSystemFinder): | ||
search_dirs: list[str] | ||
|
||
def __init__(self, app_names=None, *args, **kwargs): | ||
self.locations = [] | ||
self.storages = {} | ||
for root in self.search_dirs: | ||
if isinstance(root, (list, tuple)): | ||
prefix, root = root | ||
else: | ||
prefix = "" | ||
if (prefix, root) not in self.locations: | ||
self.locations.append((prefix, root)) | ||
for prefix, root in self.locations: | ||
filesystem_storage = FileSystemStorage(location=root) | ||
filesystem_storage.prefix = prefix | ||
self.storages[root] = filesystem_storage | ||
super(BaseFinder, self).__init__(*args, **kwargs) | ||
|
||
def check(self): | ||
raise NotImplementedError | ||
|
||
|
||
class BaseAppDirectoriesFinder(AppDirectoriesFinder): | ||
def check(self): | ||
raise NotImplementedError |
85 changes: 85 additions & 0 deletions
85
anfema_django_testutils/contrib/fixtures/finders/fixture.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
from typing import Generator, Union | ||
|
||
import django.contrib.staticfiles.finders | ||
from django.apps import apps | ||
from django.contrib.staticfiles.finders import BaseFinder | ||
from django.contrib.staticfiles.utils import matches_patterns | ||
from django.core.files.storage import FileSystemStorage | ||
|
||
from ..settings import get_config | ||
from .base import BaseAppDirectoriesFinder, BaseFileSystemFinder | ||
|
||
|
||
searched_locations = django.contrib.staticfiles.finders.searched_locations | ||
|
||
|
||
class FileSystemFinder(BaseFileSystemFinder): | ||
"""A fixture files finder that that uses the ``FIXTURES_DIRS`` setting to locate fixture files.""" | ||
|
||
search_dirs = get_config()["FIXTURE_DIRS"] | ||
|
||
def list( | ||
self, fixture_suffixes: list[str] = None, ignore_patterns: list[str] = None | ||
) -> Generator[tuple[str, FileSystemStorage], None, None]: | ||
"""Yields all fixture files found in the folder defined by the ``FIXTURES_DIRS`` setting. | ||
:param list fixture_suffixes: A list of file suffixes to match for fixture files. | ||
:param list ignore_patterns: A list of patterns to ignore when searching for fixtures. | ||
""" | ||
patterns = [f"*{suffix}" for suffix in (fixture_suffixes or apps.get_app_config("fixtures").fixture_suffixes)] | ||
yield from filter(lambda p: matches_patterns(p[0], patterns), super().list(ignore_patterns)) | ||
|
||
|
||
class AppDirectoriesFinder(BaseAppDirectoriesFinder): | ||
"""A fixture files finder that looks in the :file:`fixtures` folder of each app.""" | ||
|
||
def __init__(self, app_names=None, *args, **kwargs): | ||
self.source_dir = apps.get_app_config("fixtures").fixture_source_dir | ||
super().__init__(app_names=None, *args, **kwargs) | ||
|
||
def list( | ||
self, fixture_suffixes: list[str] = None, ignore_patterns: list[str] = None | ||
) -> Generator[tuple[str, FileSystemStorage], None, None]: | ||
"""Yields all fixture files found in the :file:`fixtures` folder of each app. | ||
:param list fixture_suffixes: A list of file suffixes to match for fixture files. | ||
:param list ignore_patterns: A list of patterns to ignore when searching for fixtures. | ||
""" | ||
|
||
patterns = [f"*{suffix}" for suffix in (fixture_suffixes or apps.get_app_config("fixtures").fixture_suffixes)] | ||
yield from filter(lambda p: matches_patterns(p[0], patterns), super().list(ignore_patterns)) | ||
|
||
|
||
def find(path: str, all: bool = False) -> Union[str, list[str], None]: | ||
"""Find a fixture file with the given path using all enabled fixture files finders. | ||
:param str path: Path to search for. | ||
:param bool all: Defines whether to return only the first match or search for all matches. | ||
:return: If ``all`` is ``False`` (default), return the first matching absolute path (or ``None`` | ||
if no match). Otherwise return a list. | ||
""" | ||
searched_locations[:] = [] | ||
matches = [] | ||
for finder in get_finders(): | ||
result = finder.find(path, all=all) | ||
|
||
if not all and result: | ||
return result | ||
if not isinstance(result, (list, tuple)): | ||
result = [result] | ||
matches.extend(result) | ||
return matches or ([] if all else None) | ||
|
||
|
||
def get_finders() -> Generator[BaseFinder, None, None]: | ||
"""Yield enabled fixture files finder classes.""" | ||
finders = get_config()["FIXTURE_FINDERS"] | ||
yield from map(get_finder, finders) | ||
|
||
|
||
def get_finder(import_path) -> BaseFinder: | ||
"""Import a given fixture files finder class. | ||
:param str import_path: The full Python path to the class to be imported | ||
""" | ||
return django.contrib.staticfiles.finders.get_finder(import_path) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
from __future__ import annotations | ||
|
||
from typing import Union | ||
|
||
from django.apps import apps | ||
|
||
from ..settings import get_config | ||
from .base import BaseAppDirectoriesFinder, BaseFileSystemFinder, find_file | ||
|
||
|
||
class FileSystemFinder(BaseFileSystemFinder): | ||
"""A media files finder that uses the ``FIXTURE_MEDIAFILE_DIRS`` setting to locate files.""" | ||
|
||
search_dirs = get_config()["FIXTURE_MEDIAFILE_DIRS"] | ||
|
||
|
||
class AppDirectoriesFinder(BaseAppDirectoriesFinder): | ||
"""A media files finder that looks in the :file:`fixtures/media` folder of each app.""" | ||
|
||
def __init__(self, app_names=None, *args, **kwargs): | ||
self.source_dir = apps.get_app_config("fixtures").fixturemedia_source_dir | ||
super().__init__(app_names=None, *args, **kwargs) | ||
|
||
|
||
def find(path: str, all: bool = False) -> Union[str, list[str], None]: | ||
"""Find a fixture media file with the given path using all enabled fixture media files finders. | ||
:param str path: Path to search for. | ||
:param bool all: Defines whether to return only the first match or search for all matches. | ||
:return: If ``all`` is ``False`` (default), return the first matching absolute path (or ``None`` | ||
if no match). Otherwise return a list. | ||
""" | ||
return find_file(get_config()["FIXTURE_MEDIAFILE_FINDERS"], path, all) |
Empty file.
Empty file.
38 changes: 38 additions & 0 deletions
38
anfema_django_testutils/contrib/fixtures/management/commands/collectfixturemedia.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
from django.core.files.storage import FileSystemStorage | ||
from django.core.management.base import BaseCommand | ||
|
||
from anfema_django_testutils.contrib.fixtures.finders.base import get_finders | ||
from anfema_django_testutils.contrib.fixtures.settings import get_config | ||
|
||
|
||
class Command(BaseCommand): | ||
"""Copies fixture media files from different locations to the settings.MEDIA_ROOT.""" | ||
|
||
help = "Collect fixture media files in a single location." | ||
verbosity: int | ||
|
||
def __init__(self, *args, **kwargs): | ||
super().__init__(*args, **kwargs) | ||
self.media_storage = FileSystemStorage() | ||
self.ignore_patterns = [] | ||
|
||
def log(self, msg: str, *, level: int = 1, style=None) -> None: | ||
"""Small log helper""" | ||
if self.verbosity >= level: | ||
self.stdout.write((style or str)(msg)) | ||
|
||
def set_options(self, **options) -> None: | ||
self.verbosity = options["verbosity"] | ||
|
||
def handle(self, **options): | ||
self.set_options(**options) | ||
|
||
finders = get_config()["FIXTURE_MEDIAFILE_FINDERS"] | ||
for finder_class in get_finders(finders): | ||
for path, storage in finder_class.list(self.ignore_patterns): | ||
source_path = storage.path(path) | ||
|
||
self.media_storage.delete(path) | ||
with storage.open(source_path) as fp: | ||
self.log(f"Copy {source_path} -> {self.media_storage.path(path)}") | ||
self.media_storage.save(path, fp) |
Oops, something went wrong.