punit 1.1.9__tar.gz → 1.2.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.
Files changed (37) hide show
  1. {punit-1.1.9/punit.egg-info → punit-1.2.0}/PKG-INFO +31 -5
  2. {punit-1.1.9 → punit-1.2.0}/README.md +1 -1
  3. {punit-1.1.9 → punit-1.2.0}/pyproject.toml +15 -4
  4. {punit-1.1.9 → punit-1.2.0/src/pUnit.egg-info}/PKG-INFO +31 -5
  5. punit-1.2.0/src/pUnit.egg-info/SOURCES.txt +31 -0
  6. punit-1.2.0/src/pUnit.egg-info/requires.txt +5 -0
  7. punit-1.2.0/src/pUnit.egg-info/top_level.txt +1 -0
  8. {punit-1.1.9 → punit-1.2.0/src}/punit/TestResult.py +42 -36
  9. punit-1.2.0/src/punit/__init__.py +21 -0
  10. {punit-1.1.9 → punit-1.2.0/src}/punit/__main__.py +14 -10
  11. {punit-1.1.9 → punit-1.2.0/src}/punit/assertions/__init__.py +3 -0
  12. {punit-1.1.9 → punit-1.2.0/src}/punit/assertions/collections.py +24 -23
  13. {punit-1.1.9 → punit-1.2.0/src}/punit/assertions/exceptions.py +10 -7
  14. {punit-1.1.9 → punit-1.2.0/src}/punit/assertions/strings.py +6 -7
  15. {punit-1.1.9 → punit-1.2.0/src}/punit/cli.py +13 -13
  16. {punit-1.1.9 → punit-1.2.0/src}/punit/discovery/__init__.py +3 -2
  17. {punit-1.1.9 → punit-1.2.0/src}/punit/facts/Fact.py +9 -9
  18. {punit-1.1.9 → punit-1.2.0/src}/punit/facts/FactManager.py +7 -8
  19. {punit-1.1.9 → punit-1.2.0/src}/punit/facts/__init__.py +6 -0
  20. {punit-1.1.9 → punit-1.2.0/src}/punit/reports/HtmlReportGenerator.py +4 -3
  21. punit-1.2.0/src/punit/reports/JUnitReportGenerator.py +196 -0
  22. {punit-1.1.9 → punit-1.2.0/src}/punit/theories/Theory.py +10 -9
  23. {punit-1.1.9 → punit-1.2.0/src}/punit/theories/TheoryManager.py +7 -8
  24. {punit-1.1.9 → punit-1.2.0/src}/punit/theories/__init__.py +6 -0
  25. {punit-1.1.9 → punit-1.2.0/src}/punit/traits/Trait.py +4 -5
  26. {punit-1.1.9 → punit-1.2.0/src}/punit/traits/__init__.py +5 -0
  27. punit-1.1.9/punit/__init__.py +0 -9
  28. punit-1.1.9/punit/reports/JUnitReportGenerator.py +0 -185
  29. punit-1.1.9/punit.egg-info/SOURCES.txt +0 -30
  30. punit-1.1.9/punit.egg-info/top_level.txt +0 -4
  31. {punit-1.1.9 → punit-1.2.0}/.scripts/punit +0 -0
  32. {punit-1.1.9 → punit-1.2.0}/LICENSE +0 -0
  33. {punit-1.1.9 → punit-1.2.0}/setup.cfg +0 -0
  34. {punit-1.1.9/punit.egg-info → punit-1.2.0/src/pUnit.egg-info}/dependency_links.txt +0 -0
  35. {punit-1.1.9 → punit-1.2.0/src}/punit/discovery/TestModuleDiscovery.py +0 -0
  36. {punit-1.1.9 → punit-1.2.0/src}/punit/reports/__init__.py +0 -0
  37. {punit-1.1.9 → punit-1.2.0/src}/punit/runner.py +0 -0
@@ -1,21 +1,47 @@
1
1
  Metadata-Version: 2.4
2
- Name: punit
3
- Version: 1.1.9
2
+ Name: pUnit
3
+ Version: 1.2.0
4
4
  Summary: A modernized unit-test framework for Python.
5
5
  Author-email: Shaun Wilson <mrshaunwilson@msn.com>
6
+ License: MIT License
7
+
8
+ Copyright (c) Shaun Wilson
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
6
28
  Project-URL: Documentation, https://punit.readthedocs.io/
7
29
  Project-URL: Homepage, https://github.com/wilson0x4d/punit
8
30
  Project-URL: Repository, https://github.com/wilson0x4d/punit.git
9
- Keywords: test,unittest,unit-test
10
- Classifier: License :: OSI Approved :: MIT License
31
+ Keywords: test,unittest,unit-test,xUnit,nUnit,pytest
11
32
  Classifier: Operating System :: OS Independent
12
33
  Classifier: Programming Language :: Python :: 3.12
13
34
  Classifier: Programming Language :: Python :: 3.13
35
+ Classifier: Programming Language :: Python :: 3.14
14
36
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
15
37
  Classifier: Intended Audience :: Developers
16
38
  Requires-Python: >=3.12
17
39
  Description-Content-Type: text/markdown
18
40
  License-File: LICENSE
41
+ Provides-Extra: dev
42
+ Requires-Dist: build; extra == "dev"
43
+ Requires-Dist: setuptools; extra == "dev"
44
+ Requires-Dist: twine; extra == "dev"
19
45
  Dynamic: license-file
20
46
 
21
47
  `pUnit` is a modernized unit-testing framework for Python, inspired by xUnit.
@@ -120,7 +146,7 @@ You can write tests as functions, class methods, instance methods, or static met
120
146
 
121
147
  ```python
122
148
 
123
- from punit import *
149
+ from punit import fact, theory, inlinedata
124
150
 
125
151
  @fact
126
152
  async def MyLibrary_WhenInitialized_TouchMustReturnTrue:
@@ -100,7 +100,7 @@ You can write tests as functions, class methods, instance methods, or static met
100
100
 
101
101
  ```python
102
102
 
103
- from punit import *
103
+ from punit import fact, theory, inlinedata
104
104
 
105
105
  @fact
106
106
  async def MyLibrary_WhenInitialized_TouchMustReturnTrue:
@@ -1,21 +1,31 @@
1
1
  [project]
2
- name = "punit"
3
- version = "1.1.9"
2
+ name = "pUnit"
3
+ version = "1.2.0"
4
4
  description = "A modernized unit-test framework for Python."
5
- keywords = ["test", "unittest", "unit-test"]
5
+ keywords = ["test", "unittest", "unit-test", "xUnit", "nUnit", "pytest"]
6
6
  authors = [
7
7
  { name="Shaun Wilson", email="mrshaunwilson@msn.com" }
8
8
  ]
9
9
  readme = "README.md"
10
+ license = {file = "LICENSE"}
10
11
  requires-python = ">=3.12"
11
12
  classifiers = [
12
- "License :: OSI Approved :: MIT License",
13
13
  "Operating System :: OS Independent",
14
14
  "Programming Language :: Python :: 3.12",
15
15
  "Programming Language :: Python :: 3.13",
16
+ "Programming Language :: Python :: 3.14",
16
17
  "Topic :: Software Development :: Libraries :: Python Modules",
17
18
  "Intended Audience :: Developers"
18
19
  ]
20
+ dependencies = [
21
+ ]
22
+
23
+ [project.optional-dependencies]
24
+ dev = [
25
+ "build",
26
+ "setuptools",
27
+ "twine"
28
+ ]
19
29
 
20
30
  [project.urls]
21
31
  Documentation = "https://punit.readthedocs.io/"
@@ -27,6 +37,7 @@ requires = ["setuptools"]
27
37
  build-backend = "setuptools.build_meta"
28
38
 
29
39
  [tool.setuptools.packages.find]
40
+ where = ["src"]
30
41
  exclude = [
31
42
  "docs",
32
43
  "tests",
@@ -1,21 +1,47 @@
1
1
  Metadata-Version: 2.4
2
- Name: punit
3
- Version: 1.1.9
2
+ Name: pUnit
3
+ Version: 1.2.0
4
4
  Summary: A modernized unit-test framework for Python.
5
5
  Author-email: Shaun Wilson <mrshaunwilson@msn.com>
6
+ License: MIT License
7
+
8
+ Copyright (c) Shaun Wilson
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
6
28
  Project-URL: Documentation, https://punit.readthedocs.io/
7
29
  Project-URL: Homepage, https://github.com/wilson0x4d/punit
8
30
  Project-URL: Repository, https://github.com/wilson0x4d/punit.git
9
- Keywords: test,unittest,unit-test
10
- Classifier: License :: OSI Approved :: MIT License
31
+ Keywords: test,unittest,unit-test,xUnit,nUnit,pytest
11
32
  Classifier: Operating System :: OS Independent
12
33
  Classifier: Programming Language :: Python :: 3.12
13
34
  Classifier: Programming Language :: Python :: 3.13
35
+ Classifier: Programming Language :: Python :: 3.14
14
36
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
15
37
  Classifier: Intended Audience :: Developers
16
38
  Requires-Python: >=3.12
17
39
  Description-Content-Type: text/markdown
18
40
  License-File: LICENSE
41
+ Provides-Extra: dev
42
+ Requires-Dist: build; extra == "dev"
43
+ Requires-Dist: setuptools; extra == "dev"
44
+ Requires-Dist: twine; extra == "dev"
19
45
  Dynamic: license-file
20
46
 
21
47
  `pUnit` is a modernized unit-testing framework for Python, inspired by xUnit.
@@ -120,7 +146,7 @@ You can write tests as functions, class methods, instance methods, or static met
120
146
 
121
147
  ```python
122
148
 
123
- from punit import *
149
+ from punit import fact, theory, inlinedata
124
150
 
125
151
  @fact
126
152
  async def MyLibrary_WhenInitialized_TouchMustReturnTrue:
@@ -0,0 +1,31 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ .scripts/punit
5
+ src/pUnit.egg-info/PKG-INFO
6
+ src/pUnit.egg-info/SOURCES.txt
7
+ src/pUnit.egg-info/dependency_links.txt
8
+ src/pUnit.egg-info/requires.txt
9
+ src/pUnit.egg-info/top_level.txt
10
+ src/punit/TestResult.py
11
+ src/punit/__init__.py
12
+ src/punit/__main__.py
13
+ src/punit/cli.py
14
+ src/punit/runner.py
15
+ src/punit/assertions/__init__.py
16
+ src/punit/assertions/collections.py
17
+ src/punit/assertions/exceptions.py
18
+ src/punit/assertions/strings.py
19
+ src/punit/discovery/TestModuleDiscovery.py
20
+ src/punit/discovery/__init__.py
21
+ src/punit/facts/Fact.py
22
+ src/punit/facts/FactManager.py
23
+ src/punit/facts/__init__.py
24
+ src/punit/reports/HtmlReportGenerator.py
25
+ src/punit/reports/JUnitReportGenerator.py
26
+ src/punit/reports/__init__.py
27
+ src/punit/theories/Theory.py
28
+ src/punit/theories/TheoryManager.py
29
+ src/punit/theories/__init__.py
30
+ src/punit/traits/Trait.py
31
+ src/punit/traits/__init__.py
@@ -0,0 +1,5 @@
1
+
2
+ [dev]
3
+ build
4
+ setuptools
5
+ twine
@@ -0,0 +1 @@
1
+ punit
@@ -2,12 +2,13 @@
2
2
  # SPDX-License-Identifier: MIT
3
3
 
4
4
  import sys
5
- from typing import TextIO
5
+ from typing import Any, TextIO, Optional, cast
6
+
6
7
 
7
8
  class TextIOCapture:
8
9
 
9
10
  __quiet:bool
10
- output:str = None
11
+ output:str|None = None
11
12
  target:TextIO
12
13
 
13
14
  def __init__(self, target:TextIO, quiet:bool = False) -> None:
@@ -23,21 +24,22 @@ class TextIOCapture:
23
24
  if not self.__quiet:
24
25
  self.target.write(text)
25
26
 
27
+
26
28
  class TestResult:
27
29
 
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
30
+ __className:Optional[str]
31
+ __exception:Exception|None
32
+ __fileName:str|None
33
+ __hostName:str|None
34
+ __isSuccess:bool|None
35
+ __moduleName:str|None
36
+ __packageName:str|None
37
+ __properties:dict[str, Any]
38
+ __startTime:float|None
39
+ __stderrCapture:TextIOCapture|None
40
+ __stdoutCapture:TextIOCapture|None
41
+ __stopTime:float|None
42
+ __testName:str|None
41
43
 
42
44
  def __init__(self):
43
45
  self.__className = None
@@ -47,7 +49,7 @@ class TestResult:
47
49
  self.__isSuccess = None
48
50
  self.__moduleName = None
49
51
  self.__packageName = None
50
- self.__properties = {}
52
+ self.__properties = dict[str,Any]()
51
53
  self.__startTime = None
52
54
  self.__stderrCapture = None
53
55
  self.__stdoutCapture = None
@@ -55,15 +57,15 @@ class TestResult:
55
57
  self.__testName = None
56
58
 
57
59
  @property
58
- def className(self) -> str:
60
+ def className(self) -> Optional[str]:
59
61
  return self.__className
60
62
 
61
63
  @className.setter
62
- def className(self, value:str) -> None:
64
+ def className(self, value:Optional[str]) -> None:
63
65
  self.__className = value
64
66
 
65
67
  @property
66
- def exception(self) -> Exception:
68
+ def exception(self) -> Exception|None:
67
69
  return self.__exception
68
70
 
69
71
  @exception.setter
@@ -71,7 +73,7 @@ class TestResult:
71
73
  self.__exception = value
72
74
 
73
75
  @property
74
- def fileName(self) -> str:
76
+ def fileName(self) -> str|None:
75
77
  return self.__fileName
76
78
 
77
79
  @fileName.setter
@@ -79,7 +81,7 @@ class TestResult:
79
81
  self.__fileName = value
80
82
 
81
83
  @property
82
- def hostName(self) -> str:
84
+ def hostName(self) -> str|None:
83
85
  return self.__hostName
84
86
 
85
87
  @hostName.setter
@@ -88,7 +90,7 @@ class TestResult:
88
90
 
89
91
  @property
90
92
  def isSuccess(self) -> bool:
91
- return self.__isSuccess
93
+ return False if self.__isSuccess is None else self.__isSuccess
92
94
 
93
95
  @isSuccess.setter
94
96
  def isSuccess(self, value:bool) -> None:
@@ -96,14 +98,14 @@ class TestResult:
96
98
 
97
99
  @property
98
100
  def moduleName(self) -> str:
99
- return self.__moduleName
101
+ return cast(str,self.__moduleName)
100
102
 
101
103
  @moduleName.setter
102
104
  def moduleName(self, value:str) -> None:
103
105
  self.__moduleName = value
104
106
 
105
107
  @property
106
- def packageName(self) -> str:
108
+ def packageName(self) -> str|None:
107
109
  return self.__packageName
108
110
 
109
111
  @packageName.setter
@@ -111,7 +113,7 @@ class TestResult:
111
113
  self.__packageName = value
112
114
 
113
115
  @property
114
- def properties(self) -> dict[str, str]:
116
+ def properties(self) -> dict[str, Any]:
115
117
  return self.__properties
116
118
 
117
119
  @properties.setter
@@ -119,7 +121,7 @@ class TestResult:
119
121
  self.__properties = value
120
122
 
121
123
  @property
122
- def startTime(self) -> float:
124
+ def startTime(self) -> float|None:
123
125
  return self.__startTime
124
126
 
125
127
  @startTime.setter
@@ -127,15 +129,15 @@ class TestResult:
127
129
  self.__startTime = value
128
130
 
129
131
  @property
130
- def stderr(self) -> str:
132
+ def stderr(self) -> str|None:
131
133
  return None if self.__stderrCapture is None else self.__stderrCapture.output
132
134
 
133
135
  @property
134
- def stdout(self) -> str:
136
+ def stdout(self) -> str|None:
135
137
  return None if self.__stdoutCapture is None else self.__stdoutCapture.output
136
138
 
137
139
  @property
138
- def stopTime(self) -> float:
140
+ def stopTime(self) -> float|None:
139
141
  return self.__stopTime
140
142
 
141
143
  @stopTime.setter
@@ -143,7 +145,7 @@ class TestResult:
143
145
  self.__stopTime = value
144
146
 
145
147
  @property
146
- def testName(self) -> str:
148
+ def testName(self) -> str|None:
147
149
  return self.__testName
148
150
 
149
151
  @testName.setter
@@ -151,13 +153,15 @@ class TestResult:
151
153
  self.__testName = value
152
154
 
153
155
  @property
154
- def took(self) -> float:
155
- return self.__stopTime - self.__startTime
156
+ def took(self) -> float|None:
157
+ return None if self.__stopTime is None or self.__startTime is None else self.__stopTime - self.__startTime
156
158
 
157
159
  @property
158
160
  def tookPretty(self) -> str:
159
161
  took = self.took
160
- if took >= 1:
162
+ if took is None:
163
+ return 'N/A'
164
+ elif took >= 1:
161
165
  return f'{took:.1f}'.rstrip('0').rstrip('.') + 's'
162
166
  elif took >= 0.001:
163
167
  return f'{(took*1000):.0f}ms'
@@ -169,12 +173,14 @@ class TestResult:
169
173
  return f'{(took*1000):.3f}'.rstrip('0').rstrip('.') + 'ms'
170
174
 
171
175
 
172
- def captureOutput(self, quiet:bool = False) -> tuple[TextIO, TextIO]:
176
+ def captureOutput(self, quiet:bool = False) -> None:
173
177
  self.__stdoutCapture = TextIOCapture(sys.stdout, quiet)
174
178
  self.__stderrCapture = TextIOCapture(sys.stderr, quiet)
175
179
  sys.stdout = self.__stdoutCapture
176
180
  sys.stderr = self.__stderrCapture
177
181
 
178
182
  def releaseOutput(self) -> None:
179
- sys.stdout = self.__stdoutCapture.target
180
- sys.stderr = self.__stderrCapture.target
183
+ if self.__stdoutCapture is not None and self.__stdoutCapture.target is not None:
184
+ sys.stdout = self.__stdoutCapture.target
185
+ if self.__stderrCapture is not None and self.__stderrCapture.target is not None:
186
+ sys.stderr = self.__stderrCapture.target
@@ -0,0 +1,21 @@
1
+ # SPDX-FileCopyrightText: © Shaun Wilson
2
+ # SPDX-License-Identifier: MIT
3
+
4
+ __version__ = '1.2.0'
5
+
6
+ from .assertions import *
7
+ from .facts import *
8
+ from .theories import *
9
+ from .traits import *
10
+
11
+
12
+ __all__ = [
13
+ 'assertions',
14
+ 'collections', 'exceptions', 'strings',
15
+ 'facts',
16
+ 'fact',
17
+ 'theories',
18
+ 'theory', 'inlinedata',
19
+ 'traits',
20
+ 'trait'
21
+ ]
@@ -2,10 +2,13 @@
2
2
  # SPDX-License-Identifier: MIT
3
3
 
4
4
  import asyncio
5
- from .cli import *
6
- from .discovery import *
5
+ import os
6
+ import time
7
+ from .cli import CommandLineInterface
8
+ from .discovery import TestModuleDiscovery
7
9
  from .reports import HtmlReportGenerator, JUnitReportGenerator
8
- from .runner import *
10
+ from .runner import TestRunner
11
+
9
12
 
10
13
  async def async_main():
11
14
  ts = time.time()
@@ -33,18 +36,19 @@ async def async_main():
33
36
  if not cli.quiet:
34
37
  print(f'Total: {len(results)}, Failures: {failureCount}, Took: {totalTime:.3f}s')
35
38
  if cli.reportFormat is not None:
36
- report:str = None
39
+ report:str|None = None
37
40
  match cli.reportFormat:
38
41
  case 'html':
39
42
  report = HtmlReportGenerator().generate(results)
40
43
  case 'junit':
41
44
  report = JUnitReportGenerator().generate(results)
42
- if cli.outputFilename is None:
43
- print(report)
44
- else:
45
- with open(cli.outputFilename, 'wb') as file:
46
- file.write(report.encode())
47
- print(f'\n("{cli.reportFormat}" report written to: {cli.outputFilename})')
45
+ if report is not None:
46
+ if cli.outputFilename is None:
47
+ print(report)
48
+ else:
49
+ with open(cli.outputFilename, 'wb') as file:
50
+ file.write(report.encode())
51
+ print(f'\n("{cli.reportFormat}" report written to: {cli.outputFilename})')
48
52
  if failureCount > 0:
49
53
  exit(119)
50
54
 
@@ -1,6 +1,9 @@
1
1
  # SPDX-FileCopyrightText: © Shaun Wilson
2
2
  # SPDX-License-Identifier: MIT
3
3
 
4
+ from . import collections, exceptions, strings
5
+
6
+
4
7
  __all__ = [
5
8
  'collections',
6
9
  'exceptions',
@@ -1,10 +1,10 @@
1
1
  # SPDX-FileCopyrightText: © Shaun Wilson
2
2
  # SPDX-License-Identifier: MIT
3
3
 
4
- from typing import Any, Callable, Sequence
4
+ from typing import Any, Callable, Sequence, Optional, cast
5
5
 
6
6
 
7
- def areSame(a:Sequence[Any]|None, b:Sequence[Any]|None, sort:bool=False, sortFunction:Callable[[Any], Any]=None) -> bool:
7
+ def areSame(a:Sequence[Any]|None, b:Sequence[Any]|None, sort:bool=False, sortFunction:Optional[Callable[[Any], Any]]=None) -> bool:
8
8
  """
9
9
  Check if two sequences contain the same elements in the same order.
10
10
 
@@ -20,30 +20,31 @@ def areSame(a:Sequence[Any]|None, b:Sequence[Any]|None, sort:bool=False, sortFun
20
20
  return False
21
21
  elif a is not None and b is None:
22
22
  return False
23
- elif len(a) != len(b):
24
- return False
23
+ elif a is not None and b is not None:
24
+ if len(a) != len(b):
25
+ return False
25
26
 
26
- if sort:
27
- if isinstance(a, dict) or isinstance(b, dict):
28
- sortFunction = sortFunction if sortFunction is not None else lambda e: e[0]
29
- a = sorted(a.items(), key=sortFunction)
30
- b = sorted(b.items(), key=sortFunction)
31
- else:
32
- sortFunction = sortFunction if sortFunction is not None else lambda e: e
33
- a = sorted(a, key=sortFunction)
34
- b = sorted(b, key=sortFunction)
27
+ if sort:
28
+ if isinstance(a, dict) or isinstance(b, dict):
29
+ sortFunction = sortFunction if sortFunction is not None else lambda e: e[0]
30
+ a = sorted(cast(dict,a).items(), key=sortFunction)
31
+ b = sorted(cast(dict,b).items(), key=sortFunction)
32
+ else:
33
+ sortFunction = sortFunction if sortFunction is not None else lambda e: e
34
+ a = sorted(a, key=sortFunction)
35
+ b = sorted(b, key=sortFunction)
35
36
 
36
- if isinstance(a, dict) or isinstance(b, dict):
37
- for pairs in zip(a.items(), b.items()):
38
- if not areSame(pairs[0], pairs[1]):
39
- return False
40
- else:
41
- for pairs in zip(a, b):
42
- if isinstance(pairs[0], dict) or isinstance(pairs[1], dict):
37
+ if isinstance(a, dict) or isinstance(b, dict):
38
+ for pairs in zip(cast(dict,a).items(), cast(dict,b).items()):
43
39
  if not areSame(pairs[0], pairs[1]):
44
40
  return False
45
- elif pairs[0] != pairs[1]:
46
- return False
41
+ else:
42
+ for pairs in zip(a, b):
43
+ if isinstance(pairs[0], dict) or isinstance(pairs[1], dict):
44
+ if not areSame(pairs[0], pairs[1]):
45
+ return False
46
+ elif pairs[0] != pairs[1]:
47
+ return False
47
48
  return True
48
49
 
49
50
  def hasLength(sequence:Sequence[Any]|None, expected:int|None) -> bool:
@@ -61,7 +62,7 @@ def hasLength(sequence:Sequence[Any]|None, expected:int|None) -> bool:
61
62
  return False
62
63
  elif sequence is not None and expected is None:
63
64
  return False
64
- return len(sequence) == expected
65
+ return sequence is not None and len(sequence) == expected
65
66
 
66
67
  def isNoneOrEmpty(sequence:Sequence[Any]|None) -> bool:
67
68
  """
@@ -1,21 +1,24 @@
1
1
  # SPDX-FileCopyrightText: © Shaun Wilson
2
2
  # SPDX-License-Identifier: MIT
3
3
 
4
- from typing import Callable, get_args
4
+ from typing import Any, Callable, Optional, cast, get_args
5
+
5
6
 
6
7
  def isNoneOrWhiteSpace(input:str):
7
8
  return input is None or len(input.lstrip()) == 0
8
9
 
10
+
9
11
  def isNoneOrEmpty(input:str):
10
12
  return input is None or len(input) == 0
11
13
 
14
+
12
15
  class raises[TError:Exception]:
13
16
 
14
17
  __action:Callable
15
18
  __exact:bool
16
- __expect:TError
19
+ __expect:Optional[TError|type]
17
20
 
18
- def __init__(self, action:Callable, *, exact:bool = False, expect:TError = None) -> None:
21
+ def __init__(self, action:Callable, *, exact:bool = False, expect:Optional[TError|type] = None) -> None:
19
22
  self.__action = action
20
23
  self.__exact = exact
21
24
  self.__expect = expect
@@ -25,10 +28,10 @@ class raises[TError:Exception]:
25
28
  self.__action()
26
29
  except BaseException as ex:
27
30
  expected = self.__expect
28
- if expected is None:
29
- expected = get_args(self.__orig_class__)[0]
31
+ if expected is None and hasattr(self, '__orig_class__') is not None:
32
+ expected = get_args(getattr(self, '__orig_class__'))[0]
30
33
  if self.__exact:
31
34
  return type(ex) is expected
32
- else:
33
- return issubclass(type(ex), expected)
35
+ elif expected is not None:
36
+ return issubclass(type(ex), cast(Any,expected))
34
37
  return False
@@ -14,17 +14,16 @@ def areSame(a:str|None, b:str|None) -> bool:
14
14
  Returns:
15
15
  True if the strings contain the same characters in the same order, False otherwise
16
16
  """
17
- if a is b:
18
- return True
19
- elif a is None and b is not None:
17
+ if a is None and b is not None:
20
18
  return False
21
19
  elif a is not None and b is None:
22
20
  return False
23
- elif len(a) != len(b):
24
- return False
25
- for i in range(len(a)):
26
- if a[i] != b[i]:
21
+ elif a is not None and b is not None:
22
+ if len(a) != len(b):
27
23
  return False
24
+ for i in range(len(a)):
25
+ if a[i] != b[i]:
26
+ return False
28
27
  return True
29
28
 
30
29
  def isNoneOrEmpty(string:str|None) -> bool: