strictabc 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 taviken
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,14 @@
1
+ Metadata-Version: 2.4
2
+ Name: strictabc
3
+ Version: 1.0.0
4
+ Summary: A module containing the implementation of Strict ABC classes and decorators
5
+ Author-email: Trevor Viken <trevor.viken@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/taviken/strictabc
8
+ Project-URL: Issues, https://github.com/taviken/strictabc/issues
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.10
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Dynamic: license-file
@@ -0,0 +1,11 @@
1
+ # strictabc
2
+ Repository of python package strictabc
3
+
4
+
5
+ License
6
+ -------
7
+
8
+ Licensed under the `MIT License`_.
9
+
10
+ .. _MIT License: https://github.com/taviken/strictabc/blob/main/LICENSE
11
+
@@ -0,0 +1,19 @@
1
+ [project]
2
+ name = "strictabc"
3
+ version = "1.0.0"
4
+ authors = [
5
+ { name="Trevor Viken", email="trevor.viken@gmail.com" },
6
+ ]
7
+ description = "A module containing the implementation of Strict ABC classes and decorators"
8
+ readme = "README.md"
9
+ requires-python = ">=3.10"
10
+ classifiers = [
11
+ "Programming Language :: Python :: 3",
12
+ "Operating System :: OS Independent",
13
+ ]
14
+ license = "MIT"
15
+ license-files = ["LICEN[CS]E*"]
16
+
17
+ [project.urls]
18
+ Homepage = "https://github.com/taviken/strictabc"
19
+ Issues = "https://github.com/taviken/strictabc/issues"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,3 @@
1
+ from .strict import *
2
+
3
+ __version__ = "0.0.1"
@@ -0,0 +1,152 @@
1
+ """
2
+ This module contains the implementation of Strict ABC classes and decorators
3
+ """
4
+
5
+ from abc import ABCMeta, abstractmethod, ABC
6
+ from typing import Mapping, List, Tuple, Iterable, Callable, Any
7
+ from inspect import signature
8
+ from collections import namedtuple
9
+
10
+ _sentinel = object()
11
+
12
+ miss_matched_sigs = namedtuple(
13
+ "miss_matched_sigs", ["method_name", "good_sig", "bad_sig"]
14
+ )
15
+
16
+ __strict_signature__ = "__strict_signature__"
17
+
18
+
19
+ class StrictAbstractError(AttributeError):
20
+ """Metaclass for defining Strict Abstract Base Classes (ABCs).
21
+
22
+ Use this metaclass to create an Strict ABC. An ABC can be subclassed
23
+ directly, and then acts as a mix-in class. You can also register
24
+ unrelated concrete classes (even built-in classes) and unrelated
25
+ ABCs as 'virtual subclasses' -- these and their descendants will
26
+ be considered subclasses of the registering ABC by the built-in
27
+ issubclass() function, but the registering ABC won't show up in
28
+ their MRO (Method Resolution Order) nor will method
29
+ implementations defined by the registering ABC be callable (not
30
+ even via super()).
31
+
32
+ This Class will throw an Attribute like error upon detecting un implemented
33
+ methods in the Concrete class at compile time.
34
+ """
35
+
36
+ def __init__(
37
+ self, name: str, missing: List[str], bad_sigs: List[miss_matched_sigs]
38
+ ):
39
+ missing_methods = f"Missing methods: {missing}"
40
+ missmatch = f"Missmatched signatures detected: {bad_sigs}"
41
+ msg = f"Errors in <{name}>\n{missing_methods}\n{missmatch}"
42
+ super().__init__(msg)
43
+
44
+
45
+ def strictabstract(funcobj: Callable):
46
+ """A decorator indicating strict abstract methods.
47
+
48
+ Requires that the metaclass is StrictABCMeta or derived from it. A
49
+ class that has a metaclass derived from StrictABCMeta cannot be
50
+ compiled unless all of its abstract methods are overridden.
51
+ The abstract methods can be called using any of the normal
52
+ 'super' call mechanisms. strictabstract() may be used to declare
53
+ abstract methods for properties and descriptors.
54
+
55
+ Usage:
56
+
57
+ class C(metaclass=StrictABCMeta):
58
+ @strictabstract
59
+ def my_abstract_method(self, arg1, arg2, argN):
60
+ ...
61
+ """
62
+ sig = signature(funcobj)
63
+ setattr(funcobj, __strict_signature__, sig)
64
+ funcobj.__isabstractmethod__ = True
65
+ return funcobj
66
+
67
+
68
+ class StrictABCMeta(ABCMeta):
69
+ def __new__(
70
+ mcls,
71
+ name: str,
72
+ bases: Tuple[object],
73
+ classdict: Mapping[str, object],
74
+ /,
75
+ **kwargs: Mapping[Any, Any],
76
+ ):
77
+
78
+ cls = super().__new__(mcls, name, bases, classdict, **kwargs)
79
+
80
+ mcls._check_bases(mcls, bases, classdict)
81
+
82
+ return cls
83
+
84
+ def _check_bases(
85
+ mcls, bases: Iterable[Tuple[object]], classdict: Mapping[str, object]
86
+ ):
87
+ for base in bases:
88
+
89
+ # filter on only methods, and are abstract
90
+ base_methods = dict(
91
+ (method_name, method)
92
+ for method_name, method in vars(base).items()
93
+ if callable(method) and method_name in base.__abstractmethods__
94
+ )
95
+
96
+ # check for missing and bad signatures
97
+ mcls._check_missmatch(mcls, base_methods, classdict)
98
+
99
+ def _check_missmatch(
100
+ mcls, base_methods: Mapping[str, Callable], classdict: Mapping[str, object]
101
+ ):
102
+ missing = []
103
+ bad_sigs = []
104
+ # loop through base classes
105
+ for method_name, method in base_methods.items():
106
+ # grab corresponding concrete method
107
+ concrete_method = classdict.get(method_name, _sentinel)
108
+
109
+ # if no concrete method found, append to missing and continue
110
+ if concrete_method is _sentinel:
111
+ missing.append(method_name)
112
+ continue
113
+
114
+ # get base method signature
115
+ base_sig = getattr(method, __strict_signature__)
116
+
117
+ # get signature fro concrete method
118
+ if isinstance(concrete_method, classmethod):
119
+ # concrete method is classmethod, handle grab sig differently
120
+ concrete_sig = signature(concrete_method.__func__)
121
+ else:
122
+ concrete_sig = signature(concrete_method)
123
+
124
+ # if base and concrete signatures don't match, append to bad sigs
125
+ if concrete_sig != base_sig:
126
+ bad_sig = miss_matched_sigs(
127
+ method_name=classdict["__qualname__"],
128
+ good_sig=base_sig,
129
+ bad_sig=concrete_sig,
130
+ )
131
+ bad_sigs.append(bad_sig)
132
+ continue # this onctinue added in case of future improvement
133
+
134
+ # if either missing or bad sigs found raise exception here
135
+ if missing or bad_sigs:
136
+ raise StrictAbstractError(mcls.__name__, missing, bad_sigs)
137
+
138
+
139
+ class StrictABC(metaclass=StrictABCMeta):
140
+ pass
141
+
142
+
143
+ __all__ = [
144
+ "StrictABC",
145
+ "StrictABCMeta",
146
+ "strictabstract",
147
+ "StrictAbstractError",
148
+ # adding regular abc items from package here
149
+ "ABC",
150
+ "ABCMeta",
151
+ "abstractmethod",
152
+ ]
@@ -0,0 +1,14 @@
1
+ Metadata-Version: 2.4
2
+ Name: strictabc
3
+ Version: 1.0.0
4
+ Summary: A module containing the implementation of Strict ABC classes and decorators
5
+ Author-email: Trevor Viken <trevor.viken@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/taviken/strictabc
8
+ Project-URL: Issues, https://github.com/taviken/strictabc/issues
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.10
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Dynamic: license-file
@@ -0,0 +1,10 @@
1
+ LICENSE
2
+ README.rst
3
+ pyproject.toml
4
+ src/strictabc/__init__.py
5
+ src/strictabc/strict.py
6
+ src/strictabc.egg-info/PKG-INFO
7
+ src/strictabc.egg-info/SOURCES.txt
8
+ src/strictabc.egg-info/dependency_links.txt
9
+ src/strictabc.egg-info/top_level.txt
10
+ tests/test_strict_abstract.py
@@ -0,0 +1 @@
1
+ strictabc
@@ -0,0 +1,79 @@
1
+ from strictabc import *
2
+ import pytest
3
+
4
+
5
+ @pytest.fixture
6
+ def setup():
7
+ class Foo(metaclass=StrictABCMeta):
8
+ @strictabstract
9
+ def bar(self):
10
+ pass
11
+
12
+ @strictabstract
13
+ def baz(self, a):
14
+ pass
15
+
16
+ class Foo2(StrictABC):
17
+ @strictabstract
18
+ def bar(self):
19
+ pass
20
+
21
+ @strictabstract
22
+ def baz(self, a):
23
+ pass
24
+
25
+ return Foo, Foo2
26
+
27
+
28
+ def test_strict_abc_meta(setup):
29
+
30
+ Foo, Foo2 = setup
31
+
32
+ class ConFooPass(Foo):
33
+ def bar(self):
34
+ print("happy")
35
+
36
+ def baz(self, a):
37
+ print(a)
38
+
39
+ assert ConFooPass()
40
+
41
+ class ConFooPassClass(Foo):
42
+ @classmethod
43
+ def bar(self):
44
+ print("happy")
45
+
46
+ def baz(self, a):
47
+ print(a)
48
+
49
+ assert ConFooPassClass()
50
+
51
+ class ConFooPassClass2(Foo2):
52
+ @classmethod
53
+ def bar(self):
54
+ print("happy")
55
+
56
+ def baz(self, a):
57
+ print(a)
58
+
59
+ assert ConFooPassClass2()
60
+
61
+
62
+ def test_fail_missing(setup):
63
+ Foo, _ = setup
64
+ with pytest.raises(StrictAbstractError):
65
+
66
+ class ConFooFailMissing(Foo):
67
+ pass
68
+
69
+
70
+ def test_fail_missmatch(setup):
71
+ Foo, _ = setup
72
+ with pytest.raises(StrictAbstractError):
73
+
74
+ class ConFooFailSig(Foo):
75
+ def bar(cls):
76
+ print("happy")
77
+
78
+ def baz(self, A):
79
+ print(A)