mirror of
https://github.com/JockeTF/fimfarchive.git
synced 2024-11-22 05:17:59 +01:00
Use pathlib in mappers and writers
This commit is contained in:
parent
2ad9d09515
commit
82dd078020
4 changed files with 52 additions and 44 deletions
|
@ -5,7 +5,7 @@ Mappers for Fimfarchive.
|
||||||
|
|
||||||
#
|
#
|
||||||
# Fimfarchive, preserves stories from Fimfiction.
|
# Fimfarchive, preserves stories from Fimfiction.
|
||||||
# Copyright (C) 2018 Joakim Soderlund
|
# Copyright (C) 2019 Joakim Soderlund
|
||||||
#
|
#
|
||||||
# This program is free software: you can redistribute it and/or modify
|
# This program is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -22,10 +22,10 @@ Mappers for Fimfarchive.
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
import os
|
|
||||||
import string
|
import string
|
||||||
from abc import abstractmethod
|
from abc import abstractmethod
|
||||||
from html import unescape
|
from html import unescape
|
||||||
|
from pathlib import Path
|
||||||
from typing import Dict, Generic, Optional, Set, TypeVar, Union
|
from typing import Dict, Generic, Optional, Set, TypeVar, Union
|
||||||
|
|
||||||
from arrow import api as arrow, Arrow
|
from arrow import api as arrow, Arrow
|
||||||
|
@ -107,19 +107,22 @@ class StoryDateMapper(Mapper[Optional[Arrow]]):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
class StoryPathMapper(Mapper[str]):
|
class StoryPathMapper(Mapper[Path]):
|
||||||
"""
|
"""
|
||||||
Returns a key-based file path for a story.
|
Returns a key-based file path for a story.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, directory: str) -> None:
|
def __init__(self, directory: Union[Path, str]) -> None:
|
||||||
self.directory = directory
|
"""
|
||||||
|
Constructor.
|
||||||
|
|
||||||
def __call__(self, story: Story) -> str:
|
Args:
|
||||||
directory = str(self.directory)
|
directory: The directory for the path.
|
||||||
key = str(story.key)
|
"""
|
||||||
|
self.directory = Path(directory)
|
||||||
|
|
||||||
return os.path.join(directory, key)
|
def __call__(self, story: Story) -> Path:
|
||||||
|
return self.directory / str(story.key)
|
||||||
|
|
||||||
|
|
||||||
class StorySlugMapper(Mapper[str]):
|
class StorySlugMapper(Mapper[str]):
|
||||||
|
|
|
@ -23,8 +23,8 @@ Writers for Fimfarchive.
|
||||||
|
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import os
|
from pathlib import Path
|
||||||
from typing import Callable, Optional, Union
|
from typing import Callable, Union
|
||||||
|
|
||||||
from fimfarchive.mappers import StaticMapper, StoryPathMapper
|
from fimfarchive.mappers import StaticMapper, StoryPathMapper
|
||||||
from fimfarchive.stories import Story
|
from fimfarchive.stories import Story
|
||||||
|
@ -36,8 +36,8 @@ __all__ = (
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
PathFunc = Callable[[Story], Optional[str]]
|
PathFunc = Callable[[Story], Union[None, Path, str]]
|
||||||
PathSpec = Union[None, PathFunc, str]
|
PathSpec = Union[None, Path, PathFunc, str]
|
||||||
|
|
||||||
|
|
||||||
class Writer():
|
class Writer():
|
||||||
|
@ -94,14 +94,14 @@ class DirectoryWriter(Writer):
|
||||||
"""
|
"""
|
||||||
if callable(obj):
|
if callable(obj):
|
||||||
return obj
|
return obj
|
||||||
elif isinstance(obj, str):
|
elif isinstance(obj, (Path, str)):
|
||||||
return StoryPathMapper(obj)
|
return StoryPathMapper(obj)
|
||||||
elif obj is None:
|
elif obj is None:
|
||||||
return StaticMapper(obj)
|
return StaticMapper(obj)
|
||||||
else:
|
else:
|
||||||
raise TypeError("Path must be callable or string.")
|
raise TypeError("Path must be callable or string.")
|
||||||
|
|
||||||
def check_overwrite(self, path: str) -> None:
|
def check_overwrite(self, path: Path) -> None:
|
||||||
"""
|
"""
|
||||||
Checks that a file is not overwritten unless requested.
|
Checks that a file is not overwritten unless requested.
|
||||||
|
|
||||||
|
@ -111,10 +111,10 @@ class DirectoryWriter(Writer):
|
||||||
Raises:
|
Raises:
|
||||||
FileExistsError: If overwrite is disabled and path exists.
|
FileExistsError: If overwrite is disabled and path exists.
|
||||||
"""
|
"""
|
||||||
if not self.overwrite and os.path.exists(path):
|
if not self.overwrite and path.exists():
|
||||||
raise FileExistsError("Would overwrite: '{}'." .format(path))
|
raise FileExistsError("Would overwrite: '{}'." .format(path))
|
||||||
|
|
||||||
def check_directory(self, path: str) -> None:
|
def check_directory(self, path: Path) -> None:
|
||||||
"""
|
"""
|
||||||
Checks that the path's parent directory exists.
|
Checks that the path's parent directory exists.
|
||||||
|
|
||||||
|
@ -128,16 +128,16 @@ class DirectoryWriter(Writer):
|
||||||
FileNotFoundError: If the parent directory does not exist,
|
FileNotFoundError: If the parent directory does not exist,
|
||||||
and if directory creation has been disabled.
|
and if directory creation has been disabled.
|
||||||
"""
|
"""
|
||||||
parent = os.path.dirname(path)
|
parent = path.parent
|
||||||
|
|
||||||
if os.path.isdir(parent):
|
if parent.is_dir():
|
||||||
return
|
return
|
||||||
elif self.make_dirs:
|
elif self.make_dirs:
|
||||||
os.makedirs(parent)
|
parent.mkdir(parents=True)
|
||||||
else:
|
else:
|
||||||
raise FileNotFoundError(parent)
|
raise FileNotFoundError(parent)
|
||||||
|
|
||||||
def perform_write(self, contents: bytes, path: str) -> None:
|
def perform_write(self, contents: bytes, path: Path) -> None:
|
||||||
"""
|
"""
|
||||||
Performs the actual file write.
|
Performs the actual file write.
|
||||||
|
|
||||||
|
@ -148,10 +148,9 @@ class DirectoryWriter(Writer):
|
||||||
self.check_overwrite(path)
|
self.check_overwrite(path)
|
||||||
self.check_directory(path)
|
self.check_directory(path)
|
||||||
|
|
||||||
with open(path, 'wb') as fobj:
|
path.write_bytes(contents)
|
||||||
fobj.write(contents)
|
|
||||||
|
|
||||||
def write_meta(self, story: Story, path: str) -> None:
|
def write_meta(self, story: Story, path: Path) -> None:
|
||||||
"""
|
"""
|
||||||
Prepares the story meta for writing.
|
Prepares the story meta for writing.
|
||||||
|
|
||||||
|
@ -169,7 +168,7 @@ class DirectoryWriter(Writer):
|
||||||
contents = text.encode('utf-8')
|
contents = text.encode('utf-8')
|
||||||
self.perform_write(contents, path)
|
self.perform_write(contents, path)
|
||||||
|
|
||||||
def write_data(self, story: Story, path: str) -> None:
|
def write_data(self, story: Story, path: Path) -> None:
|
||||||
"""
|
"""
|
||||||
Prepares the story data for writing.
|
Prepares the story data for writing.
|
||||||
|
|
||||||
|
@ -181,11 +180,13 @@ class DirectoryWriter(Writer):
|
||||||
self.perform_write(contents, path)
|
self.perform_write(contents, path)
|
||||||
|
|
||||||
def write(self, story: Story) -> None:
|
def write(self, story: Story) -> None:
|
||||||
meta_path = self.meta_path(story)
|
meta_target = self.meta_path(story)
|
||||||
data_path = self.data_path(story)
|
data_target = self.data_path(story)
|
||||||
|
|
||||||
if meta_path:
|
if meta_target is not None:
|
||||||
|
meta_path = Path(meta_target).resolve()
|
||||||
self.write_meta(story, meta_path)
|
self.write_meta(story, meta_path)
|
||||||
|
|
||||||
if data_path:
|
if data_target is not None:
|
||||||
|
data_path = Path(data_target).resolve()
|
||||||
self.write_data(story, data_path)
|
self.write_data(story, data_path)
|
||||||
|
|
|
@ -23,7 +23,8 @@ Mapper tests.
|
||||||
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from typing import no_type_check, Dict, Any
|
from pathlib import Path
|
||||||
|
from typing import Any, Dict
|
||||||
from unittest.mock import patch, MagicMock, PropertyMock
|
from unittest.mock import patch, MagicMock, PropertyMock
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
@ -262,23 +263,18 @@ class TestStoryPathMapper:
|
||||||
|
|
||||||
mapper = StoryPathMapper(directory)
|
mapper = StoryPathMapper(directory)
|
||||||
|
|
||||||
assert mapper(story) == path
|
assert mapper(story) == Path(path)
|
||||||
|
|
||||||
@no_type_check
|
|
||||||
def test_casts_values(self, tmpdir, story):
|
def test_casts_values(self, tmpdir, story):
|
||||||
"""
|
"""
|
||||||
Tests casts all values to string when joining.
|
Tests casts all values to string when joining.
|
||||||
"""
|
"""
|
||||||
directory = MagicMock()
|
mapper = StoryPathMapper('dir')
|
||||||
directory.__str__.return_value = 'dir'
|
|
||||||
|
|
||||||
story.key = MagicMock()
|
story.key = MagicMock()
|
||||||
story.key.__str__.return_value = 'key'
|
story.key.__str__.return_value = 'key'
|
||||||
|
|
||||||
mapper = StoryPathMapper(directory)
|
assert mapper(story) == Path('dir', 'key')
|
||||||
|
|
||||||
assert mapper(story) == os.path.join('dir', 'key')
|
|
||||||
assert directory.__str__.called_once_with()
|
|
||||||
assert story.key.__str__.called_once_with()
|
assert story.key.__str__.called_once_with()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ Writer tests.
|
||||||
|
|
||||||
#
|
#
|
||||||
# Fimfarchive, preserves stories from Fimfiction.
|
# Fimfarchive, preserves stories from Fimfiction.
|
||||||
# Copyright (C) 2015 Joakim Soderlund
|
# Copyright (C) 2019 Joakim Soderlund
|
||||||
#
|
#
|
||||||
# This program is free software: you can redistribute it and/or modify
|
# This program is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -24,6 +24,7 @@ Writer tests.
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
@ -77,7 +78,7 @@ class TestDirectoryWriter:
|
||||||
Tests `TypeError` is raised for invalid path types.
|
Tests `TypeError` is raised for invalid path types.
|
||||||
"""
|
"""
|
||||||
with pytest.raises(TypeError):
|
with pytest.raises(TypeError):
|
||||||
DirectoryWriter(meta_path=1)
|
DirectoryWriter(meta_path=1) # type: ignore
|
||||||
|
|
||||||
def test_parent_directory_creation(self, story, tmpdir):
|
def test_parent_directory_creation(self, story, tmpdir):
|
||||||
"""
|
"""
|
||||||
|
@ -146,8 +147,15 @@ class TestDirectoryWriter:
|
||||||
|
|
||||||
writer.write(story)
|
writer.write(story)
|
||||||
|
|
||||||
with open(meta_path(story), 'rt') as fobj:
|
with open(meta_path(story), 'rt') as meta_stream:
|
||||||
assert story.meta == json.load(fobj)
|
assert story.meta == json.load(meta_stream)
|
||||||
|
|
||||||
with open(data_path(story), 'rb') as fobj:
|
with open(data_path(story), 'rb') as data_stream:
|
||||||
assert story.data == fobj.read()
|
assert story.data == data_stream.read()
|
||||||
|
|
||||||
|
def test_current_directory_check(self, story):
|
||||||
|
"""
|
||||||
|
Tests directory check for current directory.
|
||||||
|
"""
|
||||||
|
writer = DirectoryWriter()
|
||||||
|
writer.check_directory(Path('key'))
|
||||||
|
|
Loading…
Reference in a new issue