utils.py 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  1. """This module contains miscellaneous helpers.
  2. It is not considered part of the public ufoLib API. It does, however,
  3. define the :py:obj:`.deprecated` decorator that is used elsewhere in
  4. the module.
  5. """
  6. from __future__ import annotations
  7. from typing import Optional, Type, TypeVar, Union, cast
  8. from collections.abc import Callable
  9. import enum
  10. import functools
  11. import warnings
  12. F = TypeVar("F", bound=Callable[..., object])
  13. FormatVersion = TypeVar("FormatVersion", bound="BaseFormatVersion")
  14. FormatVersionInput = Optional[Union[int, tuple[int, int], FormatVersion]]
  15. numberTypes = (int, float)
  16. def deprecated(msg: str = "") -> Callable[[F], F]:
  17. """Decorator factory to mark functions as deprecated with given message.
  18. >>> @deprecated("Enough!")
  19. ... def some_function():
  20. ... "I just print 'hello world'."
  21. ... print("hello world")
  22. >>> some_function()
  23. hello world
  24. >>> some_function.__doc__ == "I just print 'hello world'."
  25. True
  26. """
  27. def deprecated_decorator(func: F) -> F:
  28. @functools.wraps(func)
  29. def wrapper(*args, **kwargs):
  30. warnings.warn(
  31. f"{func.__name__} function is a deprecated. {msg}",
  32. category=DeprecationWarning,
  33. stacklevel=2,
  34. )
  35. return func(*args, **kwargs)
  36. return cast(F, wrapper)
  37. return deprecated_decorator
  38. def normalizeFormatVersion(
  39. value: FormatVersionInput, cls: Type[FormatVersion]
  40. ) -> FormatVersion:
  41. # Needed for type safety of UFOFormatVersion and GLIFFormatVersion input
  42. if value is None:
  43. return cls.default()
  44. if isinstance(value, cls):
  45. return value
  46. if isinstance(value, int):
  47. return cls((value, 0))
  48. if isinstance(value, tuple) and len(value) == 2:
  49. return cls(value)
  50. raise ValueError(f"Unsupported format version: {value!r}")
  51. # Base class for UFOFormatVersion and GLIFFormatVersion
  52. class BaseFormatVersion(tuple[int, int], enum.Enum):
  53. value: tuple[int, int]
  54. def __new__(cls: Type[FormatVersion], value: tuple[int, int]) -> BaseFormatVersion:
  55. return super().__new__(cls, value)
  56. @property
  57. def major(self) -> int:
  58. return self.value[0]
  59. @property
  60. def minor(self) -> int:
  61. return self.value[1]
  62. @classmethod
  63. def _missing_(cls, value: object) -> BaseFormatVersion:
  64. # allow to initialize a version enum from a single (major) integer
  65. if isinstance(value, int):
  66. return cls((value, 0))
  67. # or from None to obtain the current default version
  68. if value is None:
  69. return cls.default()
  70. raise ValueError(f"{value!r} is not a valid {cls.__name__}")
  71. def __str__(self) -> str:
  72. return f"{self.major}.{self.minor}"
  73. @classmethod
  74. def default(cls: Type[FormatVersion]) -> FormatVersion:
  75. # get the latest defined version (i.e. the max of all versions)
  76. return max(cls.__members__.values())
  77. @classmethod
  78. def supported_versions(cls: Type[FormatVersion]) -> frozenset[FormatVersion]:
  79. return frozenset(cls.__members__.values())
  80. if __name__ == "__main__":
  81. import doctest
  82. doctest.testmod()