Skip to content

Commit 008ae46

Browse files
authored
Merge pull request #393 from fonttools/drop-fs
don't import fs module, replace tempfs with tempfile.TemporaryDir
2 parents aa33a67 + 61779df commit 008ae46

File tree

3 files changed

+39
-13
lines changed

3 files changed

+39
-13
lines changed

src/ufoLib2/objects/font.py

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import os
44
import shutil
5+
import tempfile
56
from typing import (
67
Any,
78
Dict,
@@ -16,8 +17,6 @@
1617
cast,
1718
)
1819

19-
import fs.base
20-
import fs.tempfs
2120
from attrs import define, field
2221
from fontTools.ufoLib import UFOFileStructure, UFOReader, UFOWriter
2322

@@ -48,7 +47,7 @@
4847
_setstate_attrs,
4948
)
5049
from ufoLib2.serde import serde
51-
from ufoLib2.typing import HasIdentifier, PathLike, T
50+
from ufoLib2.typing import PATH_TYPES, HasIdentifier, PathLike, T
5251

5352

5453
def _convert_LayerSet(value: LayerSet | Iterable[Layer]) -> LayerSet:
@@ -532,7 +531,7 @@ def write(self, writer: UFOWriter, saveAs: bool | None = None) -> None:
532531

533532
def save(
534533
self,
535-
path: PathLike | fs.base.FS | None = None,
534+
path: PathLike | None = None,
536535
formatVersion: int = 3,
537536
structure: UFOFileStructure | None = None,
538537
overwrite: bool = False,
@@ -541,9 +540,8 @@ def save(
541540
"""Saves the font to ``path``.
542541
543542
Args:
544-
path: The target path. If it is None, the path from the last save (except
545-
when that was a ``fs.base.FS``) or when the font was first opened will
546-
be used.
543+
path: The target path. If it is None, the path from the last save or when
544+
the font was first opened will be used.
547545
formatVersion: The version to save the UFO as. Only version 3 is supported
548546
currently.
549547
structure (fontTools.ufoLib.UFOFileStructure): How to store the UFO.
@@ -567,8 +565,8 @@ def save(
567565
structure = self._fileStructure
568566

569567
# Normalize path unless we're given a fs.base.FS, which we pass to UFOWriter.
570-
if path is not None and not isinstance(path, fs.base.FS):
571-
path = os.path.normpath(os.fspath(path))
568+
if path is not None and isinstance(path, PATH_TYPES):
569+
path = os.fsdecode(os.path.normpath(os.fspath(path)))
572570

573571
overwritePath = tmp = None
574572

@@ -577,8 +575,8 @@ def save(
577575
if isinstance(path, str) and os.path.exists(path):
578576
if overwrite:
579577
overwritePath = path
580-
tmp = fs.tempfs.TempFS()
581-
path = tmp.getsyspath(os.path.basename(path))
578+
tmp = tempfile.TemporaryDirectory()
579+
path = os.path.join(tmp.name, os.path.basename(path))
582580
else:
583581
import errno
584582

@@ -608,12 +606,12 @@ def save(
608606
finally:
609607
# clean up the temporary directory
610608
if tmp is not None:
611-
tmp.close()
609+
tmp.cleanup()
612610

613611
# Only remember path if it isn't a fs.base.FS because not all FS objects are
614612
# OsFS with a corresponding filesystem path. E.g. think about MemoryFS.
615613
# If you want, you can call getsyspath("") method of OsFS object and set that to
616614
# self._path. But you then have to catch the fs.errors.NoSysPath and skip if
617615
# the FS object does not implement a filesystem path.
618-
if not isinstance(path, fs.base.FS):
616+
if isinstance(path, str):
619617
self._path = path

src/ufoLib2/typing.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
PathLike = Union[str, bytes, "os.PathLike[str]", "os.PathLike[bytes]"]
1313
"""Represents a path in various possible forms."""
1414

15+
# can be used with isinstance at runtime to check if something is a path
16+
PATH_TYPES = (str, bytes, os.PathLike)
17+
1518

1619
class Drawable(Protocol):
1720
"""Stand-in for an object that can draw itself with a given pen.

tests/test_ufoLib2.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import annotations
22

3+
import zipfile
34
from copy import deepcopy
45
from pathlib import Path
56
from typing import Any, Type
@@ -284,3 +285,27 @@ def test_convert_on_setattr(
284285
assert not isinstance(obj, attr_type)
285286
setattr(o, attr_name, obj)
286287
assert isinstance(getattr(o, attr_name), attr_type)
288+
289+
290+
@pytest.mark.parametrize(
291+
"structure", [ufoLib.UFOFileStructure.PACKAGE, ufoLib.UFOFileStructure.ZIP]
292+
)
293+
def test_save_again_to_existing_path(
294+
tmp_path: Path, structure: ufoLib.UFOFileStructure
295+
) -> None:
296+
font = Font()
297+
ufo_name = "foo.ufoz" if structure == ufoLib.UFOFileStructure.ZIP else "foo.ufo"
298+
font.save(tmp_path / ufo_name, structure=structure)
299+
300+
# without the overwrite flag, this should fail
301+
with pytest.raises(OSError, match="already exists"):
302+
font.save(tmp_path / ufo_name, structure=structure)
303+
304+
# with the overwrite flag, it should succeed
305+
font.save(tmp_path / ufo_name, overwrite=True, structure=structure)
306+
307+
if structure == ufoLib.UFOFileStructure.PACKAGE:
308+
assert (tmp_path / ufo_name).is_dir()
309+
else:
310+
assert (tmp_path / ufo_name).is_file()
311+
assert zipfile.is_zipfile(tmp_path / ufo_name)

0 commit comments

Comments
 (0)