punit 1.2.19__tar.gz → 1.2.23__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.
- {punit-1.2.19/src/pUnit.egg-info → punit-1.2.23}/PKG-INFO +5 -5
- {punit-1.2.19 → punit-1.2.23}/README.md +4 -4
- {punit-1.2.19 → punit-1.2.23}/pyproject.toml +1 -1
- {punit-1.2.19 → punit-1.2.23/src/pUnit.egg-info}/PKG-INFO +5 -5
- {punit-1.2.19 → punit-1.2.23}/src/pUnit.egg-info/SOURCES.txt +3 -0
- {punit-1.2.19 → punit-1.2.23}/src/punit/TestResult.py +4 -2
- {punit-1.2.19 → punit-1.2.23}/src/punit/__init__.py +2 -2
- {punit-1.2.19 → punit-1.2.23}/src/punit/__main__.py +1 -1
- {punit-1.2.19 → punit-1.2.23}/src/punit/cli.py +22 -24
- {punit-1.2.19 → punit-1.2.23}/src/punit/discovery/TestModuleDiscovery.py +2 -19
- {punit-1.2.19 → punit-1.2.23}/src/punit/discovery/__init__.py +0 -1
- {punit-1.2.19 → punit-1.2.23}/src/punit/facts/FactManager.py +10 -19
- punit-1.2.23/src/punit/filters/Filter.py +37 -0
- punit-1.2.23/src/punit/filters/FilterManager.py +70 -0
- punit-1.2.23/src/punit/filters/__init__.py +11 -0
- {punit-1.2.19 → punit-1.2.23}/src/punit/theories/TheoryManager.py +10 -19
- {punit-1.2.19 → punit-1.2.23}/.scripts/punit +0 -0
- {punit-1.2.19 → punit-1.2.23}/LICENSE +0 -0
- {punit-1.2.19 → punit-1.2.23}/setup.cfg +0 -0
- {punit-1.2.19 → punit-1.2.23}/src/pUnit.egg-info/dependency_links.txt +0 -0
- {punit-1.2.19 → punit-1.2.23}/src/pUnit.egg-info/requires.txt +0 -0
- {punit-1.2.19 → punit-1.2.23}/src/pUnit.egg-info/top_level.txt +0 -0
- {punit-1.2.19 → punit-1.2.23}/src/punit/assertions/__init__.py +0 -0
- {punit-1.2.19 → punit-1.2.23}/src/punit/assertions/collections.py +0 -0
- {punit-1.2.19 → punit-1.2.23}/src/punit/assertions/exceptions.py +0 -0
- {punit-1.2.19 → punit-1.2.23}/src/punit/assertions/strings.py +0 -0
- {punit-1.2.19 → punit-1.2.23}/src/punit/facts/Fact.py +0 -0
- {punit-1.2.19 → punit-1.2.23}/src/punit/facts/__init__.py +0 -0
- {punit-1.2.19 → punit-1.2.23}/src/punit/py.typed +0 -0
- {punit-1.2.19 → punit-1.2.23}/src/punit/reports/HtmlReportGenerator.py +0 -0
- {punit-1.2.19 → punit-1.2.23}/src/punit/reports/JUnitReportGenerator.py +0 -0
- {punit-1.2.19 → punit-1.2.23}/src/punit/reports/__init__.py +0 -0
- {punit-1.2.19 → punit-1.2.23}/src/punit/runner.py +0 -0
- {punit-1.2.19 → punit-1.2.23}/src/punit/theories/Theory.py +0 -0
- {punit-1.2.19 → punit-1.2.23}/src/punit/theories/__init__.py +0 -0
- {punit-1.2.19 → punit-1.2.23}/src/punit/traits/Trait.py +0 -0
- {punit-1.2.19 → punit-1.2.23}/src/punit/traits/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pUnit
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.23
|
|
4
4
|
Summary: A modernized unit-test framework for Python.
|
|
5
5
|
Author-email: Shaun Wilson <mrshaunwilson@msn.com>
|
|
6
6
|
License: MIT License
|
|
@@ -110,10 +110,10 @@ Options:
|
|
|
110
110
|
be locatable as modules in the named package.
|
|
111
111
|
Default: 'tests'
|
|
112
112
|
-i, --include PATTERN
|
|
113
|
-
Include
|
|
113
|
+
Include test files matching PATTERN
|
|
114
114
|
Default: '*.py'
|
|
115
115
|
-e, --exclude PATTERN
|
|
116
|
-
Exclude
|
|
116
|
+
Exclude test files matching PATTERN, overriding --include
|
|
117
117
|
Default: '__*__' (dunder files), '/.*/' (dot-directories)
|
|
118
118
|
-f, --filter PATTERN|@FILEPATH
|
|
119
119
|
Only execute tests matching PATTERN
|
|
@@ -178,9 +178,9 @@ class MyTestFixture:
|
|
|
178
178
|
def verifyCalcErrorCondition(self) -> None:
|
|
179
179
|
from punit.exceptions import raises
|
|
180
180
|
# assert errors are raised, or not
|
|
181
|
-
def calc_None():
|
|
181
|
+
def calc_None() -> None:
|
|
182
182
|
self.calc(None)
|
|
183
|
-
def calc_1()
|
|
183
|
+
def calc_1() -> None:
|
|
184
184
|
self.calc(1)
|
|
185
185
|
assert raises[Exception](calc_None)
|
|
186
186
|
assert not raises[Exception](calc_1)
|
|
@@ -64,10 +64,10 @@ Options:
|
|
|
64
64
|
be locatable as modules in the named package.
|
|
65
65
|
Default: 'tests'
|
|
66
66
|
-i, --include PATTERN
|
|
67
|
-
Include
|
|
67
|
+
Include test files matching PATTERN
|
|
68
68
|
Default: '*.py'
|
|
69
69
|
-e, --exclude PATTERN
|
|
70
|
-
Exclude
|
|
70
|
+
Exclude test files matching PATTERN, overriding --include
|
|
71
71
|
Default: '__*__' (dunder files), '/.*/' (dot-directories)
|
|
72
72
|
-f, --filter PATTERN|@FILEPATH
|
|
73
73
|
Only execute tests matching PATTERN
|
|
@@ -132,9 +132,9 @@ class MyTestFixture:
|
|
|
132
132
|
def verifyCalcErrorCondition(self) -> None:
|
|
133
133
|
from punit.exceptions import raises
|
|
134
134
|
# assert errors are raised, or not
|
|
135
|
-
def calc_None():
|
|
135
|
+
def calc_None() -> None:
|
|
136
136
|
self.calc(None)
|
|
137
|
-
def calc_1()
|
|
137
|
+
def calc_1() -> None:
|
|
138
138
|
self.calc(1)
|
|
139
139
|
assert raises[Exception](calc_None)
|
|
140
140
|
assert not raises[Exception](calc_1)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pUnit
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.23
|
|
4
4
|
Summary: A modernized unit-test framework for Python.
|
|
5
5
|
Author-email: Shaun Wilson <mrshaunwilson@msn.com>
|
|
6
6
|
License: MIT License
|
|
@@ -110,10 +110,10 @@ Options:
|
|
|
110
110
|
be locatable as modules in the named package.
|
|
111
111
|
Default: 'tests'
|
|
112
112
|
-i, --include PATTERN
|
|
113
|
-
Include
|
|
113
|
+
Include test files matching PATTERN
|
|
114
114
|
Default: '*.py'
|
|
115
115
|
-e, --exclude PATTERN
|
|
116
|
-
Exclude
|
|
116
|
+
Exclude test files matching PATTERN, overriding --include
|
|
117
117
|
Default: '__*__' (dunder files), '/.*/' (dot-directories)
|
|
118
118
|
-f, --filter PATTERN|@FILEPATH
|
|
119
119
|
Only execute tests matching PATTERN
|
|
@@ -178,9 +178,9 @@ class MyTestFixture:
|
|
|
178
178
|
def verifyCalcErrorCondition(self) -> None:
|
|
179
179
|
from punit.exceptions import raises
|
|
180
180
|
# assert errors are raised, or not
|
|
181
|
-
def calc_None():
|
|
181
|
+
def calc_None() -> None:
|
|
182
182
|
self.calc(None)
|
|
183
|
-
def calc_1()
|
|
183
|
+
def calc_1() -> None:
|
|
184
184
|
self.calc(1)
|
|
185
185
|
assert raises[Exception](calc_None)
|
|
186
186
|
assert not raises[Exception](calc_1)
|
|
@@ -22,6 +22,9 @@ src/punit/discovery/__init__.py
|
|
|
22
22
|
src/punit/facts/Fact.py
|
|
23
23
|
src/punit/facts/FactManager.py
|
|
24
24
|
src/punit/facts/__init__.py
|
|
25
|
+
src/punit/filters/Filter.py
|
|
26
|
+
src/punit/filters/FilterManager.py
|
|
27
|
+
src/punit/filters/__init__.py
|
|
25
28
|
src/punit/reports/HtmlReportGenerator.py
|
|
26
29
|
src/punit/reports/JUnitReportGenerator.py
|
|
27
30
|
src/punit/reports/__init__.py
|
|
@@ -16,13 +16,15 @@ class TextIOCapture:
|
|
|
16
16
|
self.output = None
|
|
17
17
|
self.target = target
|
|
18
18
|
|
|
19
|
-
def write(self, text:str):
|
|
19
|
+
def write(self, text:str) -> int:
|
|
20
20
|
if self.output is None:
|
|
21
21
|
self.output = text
|
|
22
22
|
else:
|
|
23
23
|
self.output += text
|
|
24
24
|
if not self.__quiet:
|
|
25
25
|
self.target.write(text)
|
|
26
|
+
return len(text)
|
|
27
|
+
|
|
26
28
|
|
|
27
29
|
|
|
28
30
|
class TestResult:
|
|
@@ -41,7 +43,7 @@ class TestResult:
|
|
|
41
43
|
__stopTime:float|None
|
|
42
44
|
__testName:str|None
|
|
43
45
|
|
|
44
|
-
def __init__(self):
|
|
46
|
+
def __init__(self) -> None:
|
|
45
47
|
self.__className = None
|
|
46
48
|
self.__exception = None
|
|
47
49
|
self.__fileName = None
|
|
@@ -5,6 +5,7 @@ import os
|
|
|
5
5
|
import sys
|
|
6
6
|
from typing import Optional
|
|
7
7
|
from . import __version__, __commit__
|
|
8
|
+
from .filters.FilterManager import FilterManager
|
|
8
9
|
from .traits import Trait
|
|
9
10
|
|
|
10
11
|
|
|
@@ -14,7 +15,6 @@ class CommandLineInterface:
|
|
|
14
15
|
__excludePatterns:list[str]
|
|
15
16
|
__excludeTraits:list[Trait]
|
|
16
17
|
__failfast:bool
|
|
17
|
-
__filterPattern:str
|
|
18
18
|
__help:bool
|
|
19
19
|
__includePatterns:list[str]
|
|
20
20
|
__includeTraits:list[Trait]
|
|
@@ -26,12 +26,11 @@ class CommandLineInterface:
|
|
|
26
26
|
__verbose:bool
|
|
27
27
|
__workdir:str|None
|
|
28
28
|
|
|
29
|
-
def __init__(self):
|
|
29
|
+
def __init__(self) -> None:
|
|
30
30
|
self.__aliases = dict[str,str]()
|
|
31
31
|
self.__excludePatterns = []
|
|
32
32
|
self.__excludeTraits = []
|
|
33
33
|
self.__failfast = False
|
|
34
|
-
self.__filterPattern = '*'
|
|
35
34
|
self.__help = False
|
|
36
35
|
self.__includePatterns = []
|
|
37
36
|
self.__includeTraits = []
|
|
@@ -45,7 +44,7 @@ class CommandLineInterface:
|
|
|
45
44
|
|
|
46
45
|
def __parse(self, argv:list[str]) -> 'CommandLineInterface':
|
|
47
46
|
aliasName:str|None = None
|
|
48
|
-
|
|
47
|
+
extractFilter:bool = False
|
|
49
48
|
extractExcludePattern:bool = False
|
|
50
49
|
extractTrait:bool = False
|
|
51
50
|
extractIncludePattern:bool = False
|
|
@@ -54,9 +53,9 @@ class CommandLineInterface:
|
|
|
54
53
|
extractReportFormat:bool = False
|
|
55
54
|
extractOutputFilename:bool = False
|
|
56
55
|
for arg in argv:
|
|
57
|
-
if
|
|
58
|
-
self.
|
|
59
|
-
|
|
56
|
+
if extractFilter:
|
|
57
|
+
self.__processFilterArg(arg)
|
|
58
|
+
extractFilter = False
|
|
60
59
|
continue
|
|
61
60
|
if extractIncludePattern:
|
|
62
61
|
self.__includePatterns.append(arg)
|
|
@@ -113,7 +112,7 @@ class CommandLineInterface:
|
|
|
113
112
|
case '-a':
|
|
114
113
|
extractAliasName = True
|
|
115
114
|
case '-f' | '--filter':
|
|
116
|
-
|
|
115
|
+
extractFilter = True
|
|
117
116
|
case '-e' | '--exclude':
|
|
118
117
|
extractExcludePattern = True
|
|
119
118
|
case '-z' | '--failfast':
|
|
@@ -138,8 +137,18 @@ class CommandLineInterface:
|
|
|
138
137
|
extractTrait = True
|
|
139
138
|
case _:
|
|
140
139
|
continue
|
|
140
|
+
|
|
141
|
+
if len(FilterManager.instance().filters) == 0:
|
|
142
|
+
FilterManager.instance().add('*')
|
|
143
|
+
|
|
141
144
|
return self
|
|
142
145
|
|
|
146
|
+
def __processFilterArg(self, arg:str) -> None:
|
|
147
|
+
if arg.startswith('@'):
|
|
148
|
+
FilterManager.instance().load(arg[1:])
|
|
149
|
+
else:
|
|
150
|
+
FilterManager.instance().add(arg)
|
|
151
|
+
|
|
143
152
|
@property
|
|
144
153
|
def aliases(self) -> dict:
|
|
145
154
|
return self.__aliases
|
|
@@ -148,10 +157,6 @@ class CommandLineInterface:
|
|
|
148
157
|
def failfast(self) -> bool:
|
|
149
158
|
return self.__failfast
|
|
150
159
|
|
|
151
|
-
@property
|
|
152
|
-
def filterPattern(self) -> str:
|
|
153
|
-
return self.__filterPattern
|
|
154
|
-
|
|
155
160
|
@property
|
|
156
161
|
def excludePatterns(self) -> list[str]:
|
|
157
162
|
return self.__excludePatterns
|
|
@@ -225,10 +230,10 @@ Options:
|
|
|
225
230
|
be locatable as modules in the named package.
|
|
226
231
|
Default: 'tests'
|
|
227
232
|
-i, --include PATTERN
|
|
228
|
-
Include
|
|
233
|
+
Include test files matching PATTERN
|
|
229
234
|
Default: '*.py'
|
|
230
235
|
-e, --exclude PATTERN
|
|
231
|
-
Exclude
|
|
236
|
+
Exclude test files matching PATTERN, overriding --include
|
|
232
237
|
Default: '__*__' (dunder files), '/.*/' (dot-directories)
|
|
233
238
|
-f, --filter PATTERN|@FILEPATH
|
|
234
239
|
Only execute tests matching PATTERN
|
|
@@ -262,21 +267,14 @@ Options:
|
|
|
262
267
|
print(f'Working Directory:\n\t{self.__workdir}')
|
|
263
268
|
print(f'Fail Fast: \n\t{"Yes" if self.__failfast else "No"}')
|
|
264
269
|
if len(self.__includePatterns) > 0:
|
|
265
|
-
print('Include
|
|
270
|
+
print('Include Files:')
|
|
266
271
|
for pattern in self.__includePatterns:
|
|
267
272
|
print(f'\t{pattern}')
|
|
268
273
|
if len(self.__excludePatterns) > 0:
|
|
269
|
-
print('Exclude
|
|
274
|
+
print('Exclude Files:')
|
|
270
275
|
for pattern in self.__excludePatterns:
|
|
271
276
|
print(f'\t{pattern}')
|
|
272
|
-
|
|
273
|
-
print(f'Filter Patterns: (from {self.__filterPattern[1:]})')
|
|
274
|
-
if not os.path.exists(os.path.realpath(self.__filterPattern[1:])):
|
|
275
|
-
print(f'\tFILTERS FILE DOES NOT EXIST, ABORTING!')
|
|
276
|
-
exit(5)
|
|
277
|
-
else:
|
|
278
|
-
print('Filter Pattern:')
|
|
279
|
-
print(f'\t{self.__filterPattern}')
|
|
277
|
+
FilterManager.instance().print()
|
|
280
278
|
|
|
281
279
|
def printVersion(self) -> None:
|
|
282
280
|
print(f'pUnit {__version__} ({__commit__})')
|
|
@@ -9,13 +9,13 @@ from ..theories import TheoryManager
|
|
|
9
9
|
from ..cli import CommandLineInterface
|
|
10
10
|
from ..traits import Trait
|
|
11
11
|
|
|
12
|
+
|
|
12
13
|
class TestModuleDiscovery:
|
|
13
14
|
|
|
14
15
|
__cli:CommandLineInterface
|
|
15
16
|
__excludePatterns:list[re.Pattern]
|
|
16
17
|
__excludeTraits:list[Trait]
|
|
17
18
|
__filenames:list[str]
|
|
18
|
-
__filterPatterns:list[re.Pattern]
|
|
19
19
|
__includePatterns:list[re.Pattern]
|
|
20
20
|
__includeTraits:list[Trait]
|
|
21
21
|
__workdir:str
|
|
@@ -30,7 +30,6 @@ class TestModuleDiscovery:
|
|
|
30
30
|
self.__convertPatternToRegex(pattern),
|
|
31
31
|
re.IGNORECASE))
|
|
32
32
|
self.__filenames = []
|
|
33
|
-
self.__filterPatterns = self.__buildFilterPatterns(cli.filterPattern)
|
|
34
33
|
self.__includePatterns = []
|
|
35
34
|
self.__excludeTraits = cli.excludeTraits
|
|
36
35
|
self.__includeTraits = cli.includeTraits
|
|
@@ -42,25 +41,11 @@ class TestModuleDiscovery:
|
|
|
42
41
|
re.IGNORECASE))
|
|
43
42
|
self.__workdir = workdir
|
|
44
43
|
|
|
45
|
-
def __buildFilterPatterns(self, input:str) -> list[re.Pattern]:
|
|
46
|
-
if input.startswith('@'):
|
|
47
|
-
# treat as a filepath containing one or more filter patterns
|
|
48
|
-
filepath = os.path.realpath(input[1:])
|
|
49
|
-
with open(filepath, 'rb') as f:
|
|
50
|
-
result = list[re.Pattern]()
|
|
51
|
-
for line in f.read().decode().splitlines():
|
|
52
|
-
if (self.__cli.verbose):
|
|
53
|
-
print(f'\t{line}')
|
|
54
|
-
result.append(re.compile(self.__convertPatternToRegex(line)))
|
|
55
|
-
return result
|
|
56
|
-
else:
|
|
57
|
-
# treat as a single filter pattern
|
|
58
|
-
return [re.compile(self.__convertPatternToRegex(input))]
|
|
59
44
|
|
|
60
45
|
def __convertPatternToRegex(self, pattern:str) -> str:
|
|
61
46
|
result = re.escape(pattern)\
|
|
62
47
|
.replace('\\\\', '/')\
|
|
63
|
-
.replace('\\*', r'
|
|
48
|
+
.replace('\\*', r'.*')\
|
|
64
49
|
.replace('?', '.')
|
|
65
50
|
return result
|
|
66
51
|
|
|
@@ -115,10 +100,8 @@ class TestModuleDiscovery:
|
|
|
115
100
|
|
|
116
101
|
def discover(self) -> list[str]:
|
|
117
102
|
FactManager.instance().excludeTraits = self.__excludeTraits
|
|
118
|
-
FactManager.instance().filterPatterns = self.__filterPatterns
|
|
119
103
|
FactManager.instance().includeTraits = self.__includeTraits
|
|
120
104
|
TheoryManager.instance().excludeTraits = self.__excludeTraits
|
|
121
|
-
TheoryManager.instance().filterPatterns = self.__filterPatterns
|
|
122
105
|
TheoryManager.instance().includeTraits = self.__includeTraits
|
|
123
106
|
if self.__cli.verbose:
|
|
124
107
|
print(f'.. starting test discovery')
|
|
@@ -5,23 +5,22 @@
|
|
|
5
5
|
import re
|
|
6
6
|
from typing import Callable, Optional
|
|
7
7
|
|
|
8
|
-
from .
|
|
8
|
+
from ..filters.FilterManager import FilterManager
|
|
9
9
|
from ..traits.Trait import Trait
|
|
10
|
+
from .Fact import Fact
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
class FactManager:
|
|
13
14
|
|
|
14
15
|
__excludeTraits:list[Trait]
|
|
15
|
-
__filterPatterns:Optional[list[re.Pattern]]
|
|
16
16
|
__instance:Optional['FactManager'] = None
|
|
17
17
|
__includeTraits:list[Trait]
|
|
18
18
|
__modules:dict[str, list[Fact]]
|
|
19
19
|
__traits:dict[Callable, list[Trait]]
|
|
20
20
|
|
|
21
|
-
def __init__(self):
|
|
21
|
+
def __init__(self) -> None:
|
|
22
22
|
if FactManager.__instance is not None:
|
|
23
23
|
raise Exception('Cannot create more than one instance of FactManager') # pragma: no cover
|
|
24
|
-
self.__filterPatterns = None
|
|
25
24
|
self.__modules = {}
|
|
26
25
|
self.__traits = {}
|
|
27
26
|
|
|
@@ -39,14 +38,6 @@ class FactManager:
|
|
|
39
38
|
def excludeTraits(self, value:list[Trait]) -> None:
|
|
40
39
|
self.__excludeTraits = value
|
|
41
40
|
|
|
42
|
-
@property
|
|
43
|
-
def filterPatterns(self) -> Optional[list[re.Pattern]]:
|
|
44
|
-
return self.__filterPatterns
|
|
45
|
-
|
|
46
|
-
@filterPatterns.setter
|
|
47
|
-
def filterPatterns(self, value:list[re.Pattern]) -> None:
|
|
48
|
-
self.__filterPatterns = value
|
|
49
|
-
|
|
50
41
|
@property
|
|
51
42
|
def includeTraits(self) -> list[Trait]:
|
|
52
43
|
return [] if self.__includeTraits is None else self.__includeTraits
|
|
@@ -77,13 +68,13 @@ class FactManager:
|
|
|
77
68
|
return l
|
|
78
69
|
|
|
79
70
|
def put(self, fact:Fact) -> None:
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
if
|
|
71
|
+
filters = FilterManager.instance().filters
|
|
72
|
+
matches_filter:bool = False
|
|
73
|
+
for filter in filters:
|
|
74
|
+
if filter.re.fullmatch(fact.filterName) is not None:
|
|
75
|
+
matches_filter = not filter.isExclude
|
|
76
|
+
break
|
|
77
|
+
if matches_filter:
|
|
87
78
|
l = self.get(fact.moduleName)
|
|
88
79
|
t = self.__traits.get(fact.target)
|
|
89
80
|
if t is not None:
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: © 2026 Shaun Wilson
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
import re
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Filter:
|
|
8
|
+
|
|
9
|
+
__isExclude:bool
|
|
10
|
+
__pattern:str
|
|
11
|
+
__re:re.Pattern
|
|
12
|
+
|
|
13
|
+
def __init__(self, pattern:str) -> None:
|
|
14
|
+
self.__isExclude = pattern.startswith('!')
|
|
15
|
+
self.__pattern = pattern
|
|
16
|
+
self.__re = self.__toRegex(pattern)
|
|
17
|
+
|
|
18
|
+
def __toRegex(self, pattern:str) -> re.Pattern:
|
|
19
|
+
if self.__isExclude:
|
|
20
|
+
pattern = pattern[1:]
|
|
21
|
+
pattern = re.escape(pattern)\
|
|
22
|
+
.replace('\\\\', '/')\
|
|
23
|
+
.replace('\\*', r'.*')\
|
|
24
|
+
.replace('?', '.')
|
|
25
|
+
return re.compile(pattern, re.IGNORECASE)
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
def isExclude(self) -> bool:
|
|
29
|
+
return self.__isExclude
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
def pattern(self) -> str:
|
|
33
|
+
return self.__pattern
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def re(self) -> re.Pattern:
|
|
37
|
+
return self.__re
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: © 2026 Shaun Wilson
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
import os
|
|
5
|
+
import select
|
|
6
|
+
import sys
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
from .Filter import Filter
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class FilterManager:
|
|
13
|
+
|
|
14
|
+
__instance:Optional['FilterManager']|None = None
|
|
15
|
+
__filters:list[Filter]
|
|
16
|
+
|
|
17
|
+
def __init__(self) -> None:
|
|
18
|
+
self.__filters = list[Filter]()
|
|
19
|
+
|
|
20
|
+
@property
|
|
21
|
+
def filters(self) -> list[Filter]:
|
|
22
|
+
return self.__filters
|
|
23
|
+
|
|
24
|
+
@staticmethod
|
|
25
|
+
def instance() -> 'FilterManager':
|
|
26
|
+
instance = FilterManager.__instance
|
|
27
|
+
if instance is None:
|
|
28
|
+
instance = FilterManager()
|
|
29
|
+
FilterManager.__instance = instance
|
|
30
|
+
return instance
|
|
31
|
+
|
|
32
|
+
def add(self, pattern:str) -> None:
|
|
33
|
+
self.__filters.append(Filter(pattern))
|
|
34
|
+
|
|
35
|
+
def remove(self, pattern:str) -> None:
|
|
36
|
+
for filter in [e for e in self.__filters]:
|
|
37
|
+
if filter.pattern == pattern:
|
|
38
|
+
self.__filters.remove(filter)
|
|
39
|
+
break
|
|
40
|
+
|
|
41
|
+
def load(self, filepath:str) -> None:
|
|
42
|
+
# treat as a filepath containing one or more filter patterns
|
|
43
|
+
lines:list[str] = []
|
|
44
|
+
if filepath == 'stdin':
|
|
45
|
+
ready, _, _ = select.select([sys.stdin], [], [], 5)
|
|
46
|
+
if not ready:
|
|
47
|
+
print('No data from stdin, aborting.')
|
|
48
|
+
sys.exit(7)
|
|
49
|
+
lines = sys.stdin.read().splitlines()
|
|
50
|
+
if len(lines) == 0:
|
|
51
|
+
print('No filters from stdin, aborting.')
|
|
52
|
+
sys.exit(6)
|
|
53
|
+
else:
|
|
54
|
+
if not os.path.exists(filepath):
|
|
55
|
+
print(f'file missing or not accessible: {filepath}')
|
|
56
|
+
sys.exit(5)
|
|
57
|
+
with open(filepath, 'rb') as f:
|
|
58
|
+
lines = f.read().decode().splitlines()
|
|
59
|
+
for line in lines:
|
|
60
|
+
line = line.split('#')[0].strip() # strip comments and prefix/postfix whitespace
|
|
61
|
+
if len(line) == 0:
|
|
62
|
+
# comments and empty lines
|
|
63
|
+
continue
|
|
64
|
+
self.__filters.append(Filter(line))
|
|
65
|
+
|
|
66
|
+
def print(self) -> None:
|
|
67
|
+
if len(FilterManager.instance().filters) > 0:
|
|
68
|
+
print('Filters:')
|
|
69
|
+
for filter in self.filters:
|
|
70
|
+
print(f'\t{filter.pattern}')
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import re
|
|
6
6
|
from typing import Callable, Optional
|
|
7
7
|
|
|
8
|
+
from ..filters.FilterManager import FilterManager
|
|
8
9
|
from ..traits.Trait import Trait
|
|
9
10
|
from .Theory import Theory
|
|
10
11
|
|
|
@@ -12,17 +13,15 @@ from .Theory import Theory
|
|
|
12
13
|
class TheoryManager:
|
|
13
14
|
|
|
14
15
|
__excludeTraits:list[Trait]
|
|
15
|
-
__filterPatterns:Optional[list[re.Pattern]]
|
|
16
16
|
__includeTraits:list[Trait]
|
|
17
17
|
__instance:Optional['TheoryManager'] = None
|
|
18
18
|
__modules:dict[str, list[Theory]]
|
|
19
19
|
__datas:dict[Callable, list[tuple]]
|
|
20
20
|
__traits:dict[Callable, list[Trait]]
|
|
21
21
|
|
|
22
|
-
def __init__(self):
|
|
22
|
+
def __init__(self) -> None:
|
|
23
23
|
if TheoryManager.__instance is not None:
|
|
24
24
|
raise Exception('Cannot create more than one instance of TheoryManager') # pragma: no cover
|
|
25
|
-
self.__filterPatterns = None
|
|
26
25
|
self.__modules = {}
|
|
27
26
|
self.__datas = {}
|
|
28
27
|
self.__traits = {}
|
|
@@ -41,14 +40,6 @@ class TheoryManager:
|
|
|
41
40
|
def excludeTraits(self, value:list[Trait]) -> None:
|
|
42
41
|
self.__excludeTraits = value
|
|
43
42
|
|
|
44
|
-
@property
|
|
45
|
-
def filterPatterns(self) -> Optional[list[re.Pattern]]:
|
|
46
|
-
return self.__filterPatterns
|
|
47
|
-
|
|
48
|
-
@filterPatterns.setter
|
|
49
|
-
def filterPatterns(self, value:list[re.Pattern]) -> None:
|
|
50
|
-
self.__filterPatterns = value
|
|
51
|
-
|
|
52
43
|
@property
|
|
53
44
|
def includeTraits(self) -> list[Trait]:
|
|
54
45
|
return [] if self.__includeTraits is None else self.__includeTraits
|
|
@@ -79,13 +70,13 @@ class TheoryManager:
|
|
|
79
70
|
return l
|
|
80
71
|
|
|
81
72
|
def put(self, theory:Theory) -> None:
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
if
|
|
73
|
+
filters = FilterManager.instance().filters
|
|
74
|
+
matches_filter:bool = False
|
|
75
|
+
for filter in filters:
|
|
76
|
+
if filter.re.fullmatch(theory.filterName) is not None:
|
|
77
|
+
matches_filter = not filter.isExclude
|
|
78
|
+
break
|
|
79
|
+
if matches_filter:
|
|
89
80
|
l = self.get(theory.moduleName)
|
|
90
81
|
d = self.__datas.get(theory.target)
|
|
91
82
|
if d is not None:
|
|
@@ -100,7 +91,7 @@ class TheoryManager:
|
|
|
100
91
|
l.append(theory)
|
|
101
92
|
|
|
102
93
|
def withData(self, target:Callable, data:tuple) -> None:
|
|
103
|
-
# TODO: data acquisition should be deferred until put() since that is where
|
|
94
|
+
# TODO: data acquisition should be deferred until put() since that is where `Filter` logic
|
|
104
95
|
# is applied, but for current implementation `@inlinedata()` is not affected. more advanced
|
|
105
96
|
# data decorators may benefit from deferral (for example, data coming from an API or DB.)
|
|
106
97
|
d = self.__datas.get(target)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|