Skip to content

Commit

Permalink
respond to review comments and drastically simplify the implementation!
Browse files Browse the repository at this point in the history
  • Loading branch information
cosmicexplorer committed Nov 18, 2019
1 parent ad1493b commit 356147e
Show file tree
Hide file tree
Showing 3 changed files with 14 additions and 24 deletions.
2 changes: 1 addition & 1 deletion src/python/pants/engine/objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ def __bool__(self) -> bool:
return bool(self.dependencies)


@sentinel_attribute('_is_union')
@sentinel_attribute
def union(cls):
"""A class decorator which other classes can specify that they can resolve to with `UnionRule`.
Expand Down
31 changes: 11 additions & 20 deletions src/python/pants/util/meta.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Copyright 2015 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).

from abc import ABC, abstractmethod, abstractproperty
from abc import ABC, abstractmethod
from dataclasses import FrozenInstanceError
from functools import wraps
from typing import Any, Callable, Optional, Type, TypeVar, Union
Expand Down Expand Up @@ -115,26 +115,23 @@ def staticproperty(func: Union[staticmethod, Callable]) -> ClassPropertyDescript
class _ClassDecoratorWithSentinelAttribute(ABC):
"""Base class to wrap a class decorator which sets a "sentinel attribute".
This functionality is exposed via the `@sentinel_attribute(name: str)` decorator.
This functionality is exposed via the `@sentinel_attribute` decorator.
"""

@abstractproperty
def sentinel_attribute(self) -> str: ...

@abstractmethod
def __call__(self, cls: type) -> type: ...

def define_instance_of(self, obj: type, **kwargs) -> type:
return type(obj.__name__, (obj,), {
self.sentinel_attribute: True,
'_sentinel_attribute_type': type(self),
**kwargs
})

def is_instance(self, obj: type) -> bool:
return hasattr(obj, self.sentinel_attribute)
return (getattr(obj, '_sentinel_attribute_type', None) == type(self))


def sentinel_attribute(attribute_name: str) -> Callable[[Callable[[type], type]], _ClassDecoratorWithSentinelAttribute]:
def sentinel_attribute(decorator: Callable[[type], type]) -> _ClassDecoratorWithSentinelAttribute:
"""Wraps a class decorator to add a "sentinel attribute" to decorated classes.
A "sentinel attribute" is an attribute added to the wrapped class decorator's result with
Expand All @@ -146,21 +143,15 @@ def sentinel_attribute(attribute_name: str) -> Callable[[Callable[[type], type]]
otherwise return.
"""

def wrapper(func: Callable[[type], type]) -> _ClassDecoratorWithSentinelAttribute:
class WrappedFunction(_ClassDecoratorWithSentinelAttribute):
@property
def sentinel_attribute(self):
return attribute_name

@wraps(func)
def __call__(self, cls: type) -> type:
return func(cls)
class WrappedFunction(_ClassDecoratorWithSentinelAttribute):
@wraps(decorator)
def __call__(self, cls: type) -> type:
return decorator(cls)

return WrappedFunction()
return wrapper
return WrappedFunction()


@sentinel_attribute('_frozen_after_init')
@sentinel_attribute
def frozen_after_init(cls: Type[_T]) -> Type[_T]:
"""Class decorator to freeze any modifications to the object after __init__() is done.
Expand Down
5 changes: 2 additions & 3 deletions tests/python/pants_test/util/test_meta.py
Original file line number Diff line number Diff line change
Expand Up @@ -255,16 +255,15 @@ def f(cls):
class SentinelAttributeTest(unittest.TestCase):

def test_sentinel_attribute(self):
@sentinel_attribute('_test_attr_name')
@sentinel_attribute
def f(cls):
return f.define_instance_of(cls)

@f
class C:
pass

self.assertEqual(f.sentinel_attribute, '_test_attr_name')
self.assertTrue(C._test_attr_name)
self.assertEqual(C._sentinel_attribute_type, type(f))
self.assertTrue(f.is_instance(C))


Expand Down

0 comments on commit 356147e

Please sign in to comment.