DEPRECATED -- Rewritten and moved to https://github.com/NiklasRosenstein/shut/. 🌊 Shore is a distribution and release management tool for pure Python packages.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

104 lines
3.2 KiB

  1. # -*- coding: utf8 -*-
  2. # Copyright (c) 2020 Niklas Rosenstein
  3. #
  4. # Permission is hereby granted, free of charge, to any person obtaining a copy
  5. # of this software and associated documentation files (the "Software"), to
  6. # deal in the Software without restriction, including without limitation the
  7. # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
  8. # sell copies of the Software, and to permit persons to whom the Software is
  9. # furnished to do so, subject to the following conditions:
  10. #
  11. # The above copyright notice and this permission notice shall be included in
  12. # all copies or substantial portions of the Software.
  13. #
  14. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  19. # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
  20. # IN THE SOFTWARE.
  21. """
  22. API for describing sanity checks around a package or monorepo configuration.
  23. """
  24. from collections import namedtuple
  25. from typing import Callable, Iterable, Generic, Type, TypeVar, Union
  26. import enum
  27. import types
  28. T = TypeVar('T')
  29. class CheckStatus(enum.IntEnum):
  30. PASSED = enum.auto() #: The check has passed.
  31. WARNING = enum.auto() #: The check is merely giving a warning.
  32. ERROR = enum.auto() #: The check has an error, something is in a bad or invalid state.
  33. CheckResult = namedtuple('CheckResult', 'status,message')
  34. Check = namedtuple('Check', 'name,result')
  35. SkipCheck = namedtuple('SkipCheck', '')
  36. def check(name: str) -> Callable[[Callable], Callable]:
  37. """
  38. Decorator for methods on a #Checker instance.
  39. """
  40. def decorator(func: Callable) -> Callable:
  41. func.__check_name__ = name
  42. return func
  43. return decorator
  44. class Checker(Generic[T]):
  45. def get_checks(self, project: 'Project', subject: T) -> Iterable[Check]:
  46. """
  47. Yield #Check objects for the *subject*. By default, all methods decorated with
  48. #check() are called.
  49. """
  50. for key in dir(self):
  51. value = getattr(self, key)
  52. check_value = value
  53. if isinstance(value, types.MethodType):
  54. check_value = value.__func__
  55. if isinstance(check_value, types.FunctionType) and hasattr(check_value, '__check_name__'):
  56. index = None
  57. for index, result in enumerate(value(project, subject)):
  58. if not isinstance(result, SkipCheck):
  59. yield Check(value.__check_name__, result)
  60. if index is None:
  61. yield Check(value.__check_name__, CheckResult(CheckStatus.PASSED, None))
  62. registry = {}
  63. def register_checker(checker: Type[Checker[T]], t: Type[T]) -> Type[Checker]:
  64. """
  65. Decorator to register a #Checker subclass.
  66. """
  67. registry.setdefault(t, []).append(checker)
  68. def get_checks(project: 'Project', obj: T) -> Iterable[Check]:
  69. """
  70. Returns all checks from the checkers registered for the type of *obj*.
  71. """
  72. for checker in registry.get(type(obj), []):
  73. yield from checker().get_checks(project, obj)
  74. __all__ = [
  75. 'CheckStatus',
  76. 'CheckResult',
  77. 'Check',
  78. 'register_checker',
  79. 'get_checks',
  80. ]