The @sealed I posted above (better version below) does what you suggest. If sealed.py, shape.py, and triangle.py are packages.
shape.py:
from dataclasses import dataclass
from sealed import sealed
@sealed('shape.Circle', 'triangle.Triangle')
@dataclass(frozen=True)
class Shape:
x: float
y: float
@dataclass(frozen=True, slots=True)
class Circle(Shape):
radius: float
@dataclass(frozen=True)
class Square(Shape):
side: float
triangle.py:
from dataclasses import dataclass
from shape import Shape
@dataclass(frozen=True)
class Triangle(Shape):
side: float
Then:
from triangle import Triangle
from shape import Circle, Square
c = Circle(1, 2, 3)
print(c)
t = Triangle(4, 5, 6)
print(t)
s = Square(7, 8, 9)
Gives (as expected):
Circle(x=1, y=2, radius=3)
Triangle(x=4, y=5, side=6)
AssertionError: `shape.Square` not in allowed class list: ('shape.Circle', 'triangle.Triangle').
Which is what you wanted I believe 
I actually used an improved version of sealed.py:
def sealed(*allowed):
def sealed_wrapper(sealed_cls):
def check(sub_cls):
full_name = sub_cls.__module__ + '.' + sub_cls.__qualname__
assert full_name in allowed, f"`{full_name}` not in allowed class list: {allowed}."
def new_1_arg(sub_cls):
check(sub_cls)
return sealed_cls.__sealed_old_new__(sub_cls)
def new_3_arg(sub_cls, *args, **kwargs):
check(sub_cls)
return sealed_cls.__sealed_old_new__(sub_cls, *args, **kwargs)
assert not hasattr(sealed_cls, '__sealed_old_new__'), \
f"@sealed cannot be applied twice, already applied to or inherited by `{sealed_cls.__name__}`."
sealed_cls.__sealed_old_new__ = sealed_cls.__new__
sealed_cls.__new__ = new_1_arg \
if len(signature(sealed_cls.__new__).parameters) == 1 \
else new_3_arg
return sealed_cls
return sealed_wrapper