punit 0.0.27__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-0.0.27/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Shaun Wilson
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.
punit-0.0.27/PKG-INFO ADDED
@@ -0,0 +1,174 @@
1
+ Metadata-Version: 2.2
2
+ Name: punit
3
+ Version: 0.0.27
4
+ Summary: A modernized unit-test framework for Python.
5
+ Author-email: Shaun Wilson <mrshaunwilson@msn.com>
6
+ Project-URL: Documentation, https://punit.readthedocs.io/
7
+ Project-URL: Homepage, https://github.com/wilson0x4d/punit
8
+ Project-URL: Repository, https://github.com/wilson0x4d/punit.git
9
+ Keywords: test,unittest,unit-test
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: Programming Language :: Python :: 3.13
14
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
15
+ Classifier: Intended Audience :: Developers
16
+ Requires-Python: >=3.12
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+
20
+ `pUnit` is a modernized unit-testing framework for Python, inspired by xUnit.
21
+
22
+ This README is only a high-level introduction to **pUnit**. For more detailed documentation, please view the official docs at [https://pUnit.readthedocs.io](https://pUnit.readthedocs.io).
23
+
24
+ ## Command-Line Usage
25
+
26
+ Running pUnit with no arguments will perform test auto-discovery and execution:
27
+
28
+ ```bash
29
+ python3 -m punit
30
+ ```
31
+
32
+ By default it will look for tests in the `tests/` directory. You can override this behavior by providing a `--test-package` argument:
33
+
34
+ ```bash
35
+ python3 -m punit --test-package elsewhere
36
+ ```
37
+
38
+ In the above example, test discovery will instead occur in an `elsewhere/` directory.
39
+
40
+ ### Report Generation
41
+
42
+ `pUnit` can generate "Test Results" reports. Currently supporting HTML and jUnit formats:
43
+
44
+ ```bash
45
+ python3 -m punit --report html
46
+ ```
47
+
48
+ Reports can also be output to a file where they can be lifted as part of a CI/CD pipeline and stored as an artifact or used for other purposes:
49
+
50
+ ```bash
51
+ python3 -m punit --report junit --output results.xml
52
+ ```
53
+
54
+ ### Syntax Help
55
+
56
+ There are more options available, passing a `--help` argument will print help text:
57
+
58
+ ```bash
59
+ python3 -m punit --help
60
+ ```
61
+ Outputs:
62
+ ```plaintext
63
+ Usage: python3 -m punit [-h|--help]
64
+ [-q|--quiet] [-v|--verbose]
65
+ [-f|--failfast]
66
+ [-p|--test-package NAME]
67
+ [-i|--include PATTERN]
68
+ [-e|--exclude PATTERN]
69
+ [-t|--filter PATTERN]
70
+ [-w|--workdir DIRECTORY]
71
+ [-n|--no-default-patterns]
72
+ [-r|--report {junit|json}]
73
+ [-o|--output FILENAME]
74
+
75
+ Options:
76
+ -h, --help Show this help text and exit
77
+ -q, --quiet Quiet output
78
+ -v, --verbose Verbose output
79
+ -f, --failfast Stop on first failure or error
80
+ -p, --test-package NAME
81
+ Use NAME as the test package, all tests should
82
+ be locatable as modules in the named package.
83
+ Default: 'tests'
84
+ -i, --include PATTERN
85
+ Include any tests matching PATTERN
86
+ Default: '*.py'
87
+ -e, --exclude PATTERN
88
+ Exclude any tests matching PATTERN, overriding --include
89
+ Default: '__*__' (dunder files), '/.*/' (dot-directories)
90
+ -t, --filter PATTERN
91
+ Only execute tests matching PATTERN
92
+ Default: '*'
93
+ -w, --working-directory DIRECTORY
94
+ Working directory (defaults to start directory)
95
+ -n, --no-default-patterns
96
+ Do not apply any default include/exclude patterns.
97
+ -r, --report {html|junit}
98
+ Generate a report to stdout using either an "html"
99
+ or "junit" format. When generating a report to stdout
100
+ all other output is suppressed, unless `--output`
101
+ is also specified.
102
+ -o, --output FILENAME
103
+ If `--report` is used, instead of writing to stdout
104
+ write to FILENAME. In this case `--report` does not
105
+ suppress any program output.
106
+ ```
107
+
108
+ ## Writing Tests
109
+
110
+ You can write tests as functions, class methods, instance methods, or static methods with all forms offering identical functionality. You can also utilize async/await syntax without any additional overhead.
111
+
112
+ **pUnit** is based upon the fundamental concepts of `Facts` and `Theories`. These are codified using decorators, aptly named `@fact` and `@theory`. Consider these examples:
113
+
114
+ ```python
115
+
116
+ from punit import *
117
+
118
+ @fact
119
+ async def MyLibrary_WhenInitialized_TouchMustReturnTrue:
120
+ mylib = MyLibrary()
121
+ mylib.initialize()
122
+ await asyncio.sleep(1)
123
+ assert mylib.touch(), "Expecting touch() to return true, because initialize() was called."
124
+
125
+ class MyTestFixture:
126
+
127
+ def calc(number:int|None = None):
128
+ if number == None:
129
+ raise Exception('Invalid value "None".')
130
+ return number * number
131
+
132
+ @theory
133
+ @inlinedata(0, 0)
134
+ @inlinedata(1, 1)
135
+ @inlinedata(2, 4)
136
+ @inlinedata(3, 9)
137
+ @inlinedata(5, 25)
138
+ @inlinedata(8, 64)
139
+ def verifyCalcAssumptions(self, number:int, expected:int) -> None:
140
+ assert expected == self.calc(number)
141
+
142
+ @fact
143
+ def verifyCalcErrorCondition(self) -> None:
144
+ from punit.exceptions import raises
145
+ # assert errors are raised, or not
146
+ def calc_None():
147
+ self.calc(None)
148
+ def calc_1()
149
+ self.calc(1)
150
+ assert raises[Exception](calc_None)
151
+ assert not raises[Exception](calc_1)
152
+ ```
153
+
154
+ Because `pUnit` is a decorative framework you are afforded the utmost freedom in how you structure and implement your tests.
155
+
156
+ Unlike other testing frameworks, the names you use for functions, classes, and methods is not relevant. There is no requirement to inherit classes from specific abstract/base classes for any particular functionality. There is no requirement that your tests be organized into modules with `__init__.py` files.
157
+
158
+ You will want to take particular note of the `--exclude` command-line parameter which allows you to restrict what `pUnit` will consider to be a valid test file. While the default behavior will fit 99% of use-cases, you _can_ exercise more control over the discovery process.
159
+
160
+ ## Vision, Future, and LTS
161
+
162
+ The long-term vision is to provide both imperative and declarative syntaxes for testing while keeping `pUnit` as simple as possible in its implementation.
163
+
164
+ `pUnit` is a Python 3.12+ package and there are no plans to backport it to earlier versions of Python, however, user contributions to support backward compatibility _will be accepted_ when it makes sense.
165
+
166
+ As Python progresses so will `pUnit` and SEMVER rules will be respected to provide developers with assurance that a major version of `pUnit` is fit for a particular purpose, thus, if there is ever a breaking change in Python that requires a breaking change in `pUnit` you can expect `pUnit` versioning to reflect this.
167
+
168
+ In any situation where an undocumented feature may be used maintainers will actively keep watch on deprecation notices and removals, will clearly identify these dependencies in the docs, and most importantly will provide a LTS alternative to any undocumented feature or such features will not be included in `pUnit`.
169
+
170
+ With respect to long-term support, we will commit to maintaining major versions for 3 years from the date they were superceded by a new major version. This should align well with Python Core Development.
171
+
172
+ ## Contact
173
+
174
+ You can reach me on [Discord](https://discordapp.com/users/307684202080501761) or [open an Issue on Github](https://github.com/wilson0x4d/punit/issues/new/choose).
punit-0.0.27/README.md ADDED
@@ -0,0 +1,155 @@
1
+ `pUnit` is a modernized unit-testing framework for Python, inspired by xUnit.
2
+
3
+ This README is only a high-level introduction to **pUnit**. For more detailed documentation, please view the official docs at [https://pUnit.readthedocs.io](https://pUnit.readthedocs.io).
4
+
5
+ ## Command-Line Usage
6
+
7
+ Running pUnit with no arguments will perform test auto-discovery and execution:
8
+
9
+ ```bash
10
+ python3 -m punit
11
+ ```
12
+
13
+ By default it will look for tests in the `tests/` directory. You can override this behavior by providing a `--test-package` argument:
14
+
15
+ ```bash
16
+ python3 -m punit --test-package elsewhere
17
+ ```
18
+
19
+ In the above example, test discovery will instead occur in an `elsewhere/` directory.
20
+
21
+ ### Report Generation
22
+
23
+ `pUnit` can generate "Test Results" reports. Currently supporting HTML and jUnit formats:
24
+
25
+ ```bash
26
+ python3 -m punit --report html
27
+ ```
28
+
29
+ Reports can also be output to a file where they can be lifted as part of a CI/CD pipeline and stored as an artifact or used for other purposes:
30
+
31
+ ```bash
32
+ python3 -m punit --report junit --output results.xml
33
+ ```
34
+
35
+ ### Syntax Help
36
+
37
+ There are more options available, passing a `--help` argument will print help text:
38
+
39
+ ```bash
40
+ python3 -m punit --help
41
+ ```
42
+ Outputs:
43
+ ```plaintext
44
+ Usage: python3 -m punit [-h|--help]
45
+ [-q|--quiet] [-v|--verbose]
46
+ [-f|--failfast]
47
+ [-p|--test-package NAME]
48
+ [-i|--include PATTERN]
49
+ [-e|--exclude PATTERN]
50
+ [-t|--filter PATTERN]
51
+ [-w|--workdir DIRECTORY]
52
+ [-n|--no-default-patterns]
53
+ [-r|--report {junit|json}]
54
+ [-o|--output FILENAME]
55
+
56
+ Options:
57
+ -h, --help Show this help text and exit
58
+ -q, --quiet Quiet output
59
+ -v, --verbose Verbose output
60
+ -f, --failfast Stop on first failure or error
61
+ -p, --test-package NAME
62
+ Use NAME as the test package, all tests should
63
+ be locatable as modules in the named package.
64
+ Default: 'tests'
65
+ -i, --include PATTERN
66
+ Include any tests matching PATTERN
67
+ Default: '*.py'
68
+ -e, --exclude PATTERN
69
+ Exclude any tests matching PATTERN, overriding --include
70
+ Default: '__*__' (dunder files), '/.*/' (dot-directories)
71
+ -t, --filter PATTERN
72
+ Only execute tests matching PATTERN
73
+ Default: '*'
74
+ -w, --working-directory DIRECTORY
75
+ Working directory (defaults to start directory)
76
+ -n, --no-default-patterns
77
+ Do not apply any default include/exclude patterns.
78
+ -r, --report {html|junit}
79
+ Generate a report to stdout using either an "html"
80
+ or "junit" format. When generating a report to stdout
81
+ all other output is suppressed, unless `--output`
82
+ is also specified.
83
+ -o, --output FILENAME
84
+ If `--report` is used, instead of writing to stdout
85
+ write to FILENAME. In this case `--report` does not
86
+ suppress any program output.
87
+ ```
88
+
89
+ ## Writing Tests
90
+
91
+ You can write tests as functions, class methods, instance methods, or static methods with all forms offering identical functionality. You can also utilize async/await syntax without any additional overhead.
92
+
93
+ **pUnit** is based upon the fundamental concepts of `Facts` and `Theories`. These are codified using decorators, aptly named `@fact` and `@theory`. Consider these examples:
94
+
95
+ ```python
96
+
97
+ from punit import *
98
+
99
+ @fact
100
+ async def MyLibrary_WhenInitialized_TouchMustReturnTrue:
101
+ mylib = MyLibrary()
102
+ mylib.initialize()
103
+ await asyncio.sleep(1)
104
+ assert mylib.touch(), "Expecting touch() to return true, because initialize() was called."
105
+
106
+ class MyTestFixture:
107
+
108
+ def calc(number:int|None = None):
109
+ if number == None:
110
+ raise Exception('Invalid value "None".')
111
+ return number * number
112
+
113
+ @theory
114
+ @inlinedata(0, 0)
115
+ @inlinedata(1, 1)
116
+ @inlinedata(2, 4)
117
+ @inlinedata(3, 9)
118
+ @inlinedata(5, 25)
119
+ @inlinedata(8, 64)
120
+ def verifyCalcAssumptions(self, number:int, expected:int) -> None:
121
+ assert expected == self.calc(number)
122
+
123
+ @fact
124
+ def verifyCalcErrorCondition(self) -> None:
125
+ from punit.exceptions import raises
126
+ # assert errors are raised, or not
127
+ def calc_None():
128
+ self.calc(None)
129
+ def calc_1()
130
+ self.calc(1)
131
+ assert raises[Exception](calc_None)
132
+ assert not raises[Exception](calc_1)
133
+ ```
134
+
135
+ Because `pUnit` is a decorative framework you are afforded the utmost freedom in how you structure and implement your tests.
136
+
137
+ Unlike other testing frameworks, the names you use for functions, classes, and methods is not relevant. There is no requirement to inherit classes from specific abstract/base classes for any particular functionality. There is no requirement that your tests be organized into modules with `__init__.py` files.
138
+
139
+ You will want to take particular note of the `--exclude` command-line parameter which allows you to restrict what `pUnit` will consider to be a valid test file. While the default behavior will fit 99% of use-cases, you _can_ exercise more control over the discovery process.
140
+
141
+ ## Vision, Future, and LTS
142
+
143
+ The long-term vision is to provide both imperative and declarative syntaxes for testing while keeping `pUnit` as simple as possible in its implementation.
144
+
145
+ `pUnit` is a Python 3.12+ package and there are no plans to backport it to earlier versions of Python, however, user contributions to support backward compatibility _will be accepted_ when it makes sense.
146
+
147
+ As Python progresses so will `pUnit` and SEMVER rules will be respected to provide developers with assurance that a major version of `pUnit` is fit for a particular purpose, thus, if there is ever a breaking change in Python that requires a breaking change in `pUnit` you can expect `pUnit` versioning to reflect this.
148
+
149
+ In any situation where an undocumented feature may be used maintainers will actively keep watch on deprecation notices and removals, will clearly identify these dependencies in the docs, and most importantly will provide a LTS alternative to any undocumented feature or such features will not be included in `pUnit`.
150
+
151
+ With respect to long-term support, we will commit to maintaining major versions for 3 years from the date they were superceded by a new major version. This should align well with Python Core Development.
152
+
153
+ ## Contact
154
+
155
+ You can reach me on [Discord](https://discordapp.com/users/307684202080501761) or [open an Issue on Github](https://github.com/wilson0x4d/punit/issues/new/choose).
@@ -0,0 +1,180 @@
1
+ # SPDX-FileCopyrightText: © 2024 Shaun Wilson
2
+ # SPDX-License-Identifier: MIT
3
+
4
+ import sys
5
+ from typing import TextIO
6
+
7
+ class TextIOCapture:
8
+
9
+ __quiet:bool
10
+ output:str = None
11
+ target:TextIO
12
+
13
+ def __init__(self, target:TextIO, quiet:bool = False) -> None:
14
+ self.__quiet = quiet
15
+ self.output = None
16
+ self.target = target
17
+
18
+ def write(self, text:str):
19
+ if self.output is None:
20
+ self.output = text
21
+ else:
22
+ self.output += text
23
+ if not self.__quiet:
24
+ self.target.write(text)
25
+
26
+ class TestResult:
27
+
28
+ __className:str
29
+ __exception:Exception
30
+ __fileName:str
31
+ __hostName:str
32
+ __isSuccess:bool
33
+ __moduleName:str
34
+ __packageName:str
35
+ __properties:dict[str, str]
36
+ __startTime:float
37
+ __stderrCapture:TextIOCapture
38
+ __stdoutCapture:TextIOCapture
39
+ __stopTime:float
40
+ __testName:str
41
+
42
+ def __init__(self):
43
+ self.__className = None
44
+ self.__exception = None
45
+ self.__fileName = None
46
+ self.__hostName = None
47
+ self.__isSuccess = None
48
+ self.__moduleName = None
49
+ self.__packageName = None
50
+ self.__properties = {}
51
+ self.__startTime = None
52
+ self.__stderrCapture = None
53
+ self.__stdoutCapture = None
54
+ self.__stopTime = None
55
+ self.__testName = None
56
+
57
+ @property
58
+ def className(self) -> str:
59
+ return self.__className
60
+
61
+ @className.setter
62
+ def className(self, value:str) -> None:
63
+ self.__className = value
64
+
65
+ @property
66
+ def exception(self) -> Exception:
67
+ return self.__exception
68
+
69
+ @exception.setter
70
+ def exception(self, value:Exception) -> None:
71
+ self.__exception = value
72
+
73
+ @property
74
+ def fileName(self) -> str:
75
+ return self.__fileName
76
+
77
+ @fileName.setter
78
+ def fileName(self, value:str) -> None:
79
+ self.__fileName = value
80
+
81
+ @property
82
+ def hostName(self) -> str:
83
+ return self.__hostName
84
+
85
+ @hostName.setter
86
+ def hostName(self, value:str) -> None:
87
+ self.__hostName = value
88
+
89
+ @property
90
+ def isSuccess(self) -> bool:
91
+ return self.__isSuccess
92
+
93
+ @isSuccess.setter
94
+ def isSuccess(self, value:bool) -> None:
95
+ self.__isSuccess = value
96
+
97
+ @property
98
+ def moduleName(self) -> str:
99
+ return self.__moduleName
100
+
101
+ @moduleName.setter
102
+ def moduleName(self, value:str) -> None:
103
+ self.__moduleName = value
104
+
105
+ @property
106
+ def packageName(self) -> str:
107
+ return self.__packageName
108
+
109
+ @packageName.setter
110
+ def packageName(self, value:str) -> None:
111
+ self.__packageName = value
112
+
113
+ @property
114
+ def properties(self) -> dict[str, str]:
115
+ return self.__properties
116
+
117
+ @properties.setter
118
+ def properties(self, value:dict[str, str]) -> None:
119
+ self.__properties = value
120
+
121
+ @property
122
+ def startTime(self) -> float:
123
+ return self.__startTime
124
+
125
+ @startTime.setter
126
+ def startTime(self, value:float) -> None:
127
+ self.__startTime = value
128
+
129
+ @property
130
+ def stderr(self) -> str:
131
+ return None if self.__stderrCapture is None else self.__stderrCapture.output
132
+
133
+ @property
134
+ def stdout(self) -> str:
135
+ return None if self.__stdoutCapture is None else self.__stdoutCapture.output
136
+
137
+ @property
138
+ def stopTime(self) -> float:
139
+ return self.__stopTime
140
+
141
+ @stopTime.setter
142
+ def stopTime(self, value:float) -> None:
143
+ self.__stopTime = value
144
+
145
+ @property
146
+ def testName(self) -> str:
147
+ return self.__testName
148
+
149
+ @testName.setter
150
+ def testName(self, value:str) -> None:
151
+ self.__testName = value
152
+
153
+ @property
154
+ def took(self) -> float:
155
+ return self.__stopTime - self.__startTime
156
+
157
+ @property
158
+ def tookPretty(self) -> str:
159
+ took = self.took
160
+ if took >= 1:
161
+ return f'{took:.1f}'.rstrip('0').rstrip('.') + 's'
162
+ elif took >= 0.001:
163
+ return f'{(took*1000):.0f}ms'
164
+ elif took >= 0.000001:
165
+ return f'{(took*1000):.3f}'.rstrip('0').rstrip('.') + 'ms'
166
+ elif took >= 0.000000001:
167
+ return f'{(took*1000000):.3f}'.rstrip('0').rstrip('.') + 'μs'
168
+ else:
169
+ return f'{(took*1000):.3f}'.rstrip('0').rstrip('.') + 'ms'
170
+
171
+
172
+ def captureOutput(self, quiet:bool = False) -> tuple[TextIO, TextIO]:
173
+ self.__stdoutCapture = TextIOCapture(sys.stdout, quiet)
174
+ self.__stderrCapture = TextIOCapture(sys.stderr, quiet)
175
+ sys.stdout = self.__stdoutCapture
176
+ sys.stderr = self.__stderrCapture
177
+
178
+ def releaseOutput(self) -> None:
179
+ sys.stdout = self.__stdoutCapture.target
180
+ sys.stderr = self.__stderrCapture.target
@@ -0,0 +1,8 @@
1
+ # SPDX-FileCopyrightText: © 2024 Shaun Wilson
2
+ # SPDX-License-Identifier: MIT
3
+
4
+ __version__ = '0.0.27'
5
+
6
+ from .assertions import *
7
+ from .facts import *
8
+ from .theories import *
@@ -0,0 +1,55 @@
1
+ # SPDX-FileCopyrightText: © 2024 Shaun Wilson
2
+ # SPDX-License-Identifier: MIT
3
+
4
+ import asyncio
5
+ from .cli import *
6
+ from .discovery import *
7
+ from .reports import HtmlReportGenerator, JUnitReportGenerator
8
+ from .runner import *
9
+
10
+ async def async_main():
11
+ ts = time.time()
12
+ cli = CommandLineInterface.parse()
13
+ if cli.help:
14
+ cli.printHelp()
15
+ elif cli.verbose and not cli.quiet:
16
+ cli.printSummary()
17
+ elif not cli.quiet:
18
+ cli.printVersion()
19
+ os.chdir(cli.workdir)
20
+ testModuleDiscovery = TestModuleDiscovery(
21
+ os.path.join(cli.workdir, cli.testPackageName),
22
+ cli.includePatterns,
23
+ cli.excludePatterns,
24
+ cli)
25
+ testModuleDiscovery.discover()
26
+ testRunner = TestRunner(cli.testPackageName, testModuleDiscovery.filenames, cli)
27
+ results = await testRunner.run()
28
+ totalTime = time.time() - ts
29
+ failureCount = 0
30
+ for result in results:
31
+ if not result.isSuccess:
32
+ failureCount += 1
33
+ if not cli.quiet:
34
+ print(f'Total: {len(results)}, Failures: {failureCount}, Took: {totalTime:.3f}s')
35
+ if cli.reportFormat is not None:
36
+ report:str = None
37
+ match cli.reportFormat:
38
+ case 'html':
39
+ report = HtmlReportGenerator().generate(results)
40
+ case 'junit':
41
+ report = JUnitReportGenerator().generate(results)
42
+ if cli.outputFilename is None:
43
+ print(report)
44
+ else:
45
+ with open(cli.outputFilename, 'wt') as file:
46
+ file.write(report)
47
+ print(f'\n("{cli.reportFormat}" report written to: {cli.outputFilename})')
48
+ if failureCount > 0:
49
+ exit(119)
50
+
51
+ def main():
52
+ asyncio.run(async_main())
53
+
54
+ if (__name__ == '__main__'):
55
+ main()
@@ -0,0 +1,7 @@
1
+ # SPDX-FileCopyrightText: © 2024 Shaun Wilson
2
+ # SPDX-License-Identifier: MIT
3
+
4
+ __all__ = [
5
+ 'collections',
6
+ 'exceptions'
7
+ ]
@@ -0,0 +1,18 @@
1
+ # SPDX-FileCopyrightText: © 2024 Shaun Wilson
2
+ # SPDX-License-Identifier: MIT
3
+
4
+ from typing import Any, Sequence
5
+
6
+ def areSame(a:Sequence[Any], b:Sequence[Any]) -> bool:
7
+ if a is b:
8
+ return True
9
+ elif a is None and b is not None:
10
+ return False
11
+ elif a is not None and b is None:
12
+ return False
13
+ elif len(a) != len(b):
14
+ return False
15
+ for i in range(len(a)):
16
+ if not a[i] == b[i]:
17
+ return False
18
+ return True
@@ -0,0 +1,33 @@
1
+ # SPDX-FileCopyrightText: © 2024 Shaun Wilson
2
+ # SPDX-License-Identifier: MIT
3
+
4
+ from typing import Callable, get_args
5
+
6
+ def isNoneOrWhiteSpace(input:str):
7
+ return input is None or len(input.lstrip()) == 0
8
+
9
+ def isNoneOrEmpty(input:str):
10
+ return input is None or len(input) == 0
11
+
12
+ class raises[TError:Exception]:
13
+
14
+ __action:Callable
15
+ __exact:bool
16
+ __expect:TError
17
+
18
+ def __init__(self, action:Callable, *, exact:bool = False, expect:TError = None) -> None:
19
+ self.__action = action
20
+ self.__exact = exact
21
+ self.__expect = expect
22
+
23
+ def __bool__(self) -> bool:
24
+ try:
25
+ self.__action()
26
+ except BaseException as ex:
27
+ expected = self.__expect
28
+ if expected is None:
29
+ expected = get_args(self.__orig_class__)[0]
30
+ if self.__exact:
31
+ return type(ex) is expected
32
+ else:
33
+ return issubclass(type(ex), expected)