assertpy2 2.3.5__tar.gz → 2.3.6__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.
- assertpy2-2.3.6/CONTRIBUTING.md +50 -0
- {assertpy2-2.3.5 → assertpy2-2.3.6}/PKG-INFO +35 -17
- {assertpy2-2.3.5 → assertpy2-2.3.6}/README.md +34 -16
- {assertpy2-2.3.5 → assertpy2-2.3.6}/assertpy2/__init__.py +2 -0
- {assertpy2-2.3.5 → assertpy2-2.3.6}/assertpy2/_typing.py +12 -0
- {assertpy2-2.3.5 → assertpy2-2.3.6}/assertpy2/assertpy.py +66 -31
- {assertpy2-2.3.5 → assertpy2-2.3.6}/assertpy2/base.py +0 -28
- {assertpy2-2.3.5 → assertpy2-2.3.6}/assertpy2/collection.py +151 -29
- {assertpy2-2.3.5 → assertpy2-2.3.6}/assertpy2/contains.py +0 -29
- {assertpy2-2.3.5 → assertpy2-2.3.6}/assertpy2/date.py +0 -28
- {assertpy2-2.3.5 → assertpy2-2.3.6}/assertpy2/dict.py +0 -28
- {assertpy2-2.3.5 → assertpy2-2.3.6}/assertpy2/dynamic.py +0 -28
- {assertpy2-2.3.5 → assertpy2-2.3.6}/assertpy2/exception.py +0 -28
- {assertpy2-2.3.5 → assertpy2-2.3.6}/assertpy2/extracting.py +0 -28
- {assertpy2-2.3.5 → assertpy2-2.3.6}/assertpy2/file.py +0 -28
- {assertpy2-2.3.5 → assertpy2-2.3.6}/assertpy2/helpers.py +0 -28
- {assertpy2-2.3.5 → assertpy2-2.3.6}/assertpy2/numeric.py +0 -28
- {assertpy2-2.3.5 → assertpy2-2.3.6}/assertpy2/snapshot.py +0 -28
- {assertpy2-2.3.5 → assertpy2-2.3.6}/assertpy2/string.py +0 -28
- {assertpy2-2.3.5 → assertpy2-2.3.6}/docs/api.md +125 -0
- {assertpy2-2.3.5 → assertpy2-2.3.6}/pyproject.toml +1 -1
- assertpy2-2.3.6/tests/test_bool.py +35 -0
- {assertpy2-2.3.5 → assertpy2-2.3.6}/tests/test_class.py +0 -28
- {assertpy2-2.3.5 → assertpy2-2.3.6}/tests/test_collection.py +0 -29
- {assertpy2-2.3.5 → assertpy2-2.3.6}/tests/test_core.py +0 -28
- {assertpy2-2.3.5 → assertpy2-2.3.6}/tests/test_custom_dict.py +0 -28
- assertpy2-2.3.6/tests/test_custom_list.py +52 -0
- {assertpy2-2.3.5 → assertpy2-2.3.6}/tests/test_datetime.py +0 -28
- assertpy2-2.3.6/tests/test_description.py +35 -0
- {assertpy2-2.3.5 → assertpy2-2.3.6}/tests/test_dict.py +0 -28
- {assertpy2-2.3.5 → assertpy2-2.3.6}/tests/test_dict_compare.py +0 -28
- {assertpy2-2.3.5 → assertpy2-2.3.6}/tests/test_dyn.py +0 -28
- {assertpy2-2.3.5 → assertpy2-2.3.6}/tests/test_equals.py +0 -28
- {assertpy2-2.3.5 → assertpy2-2.3.6}/tests/test_expected_exception.py +0 -28
- {assertpy2-2.3.5 → assertpy2-2.3.6}/tests/test_extensions.py +0 -28
- {assertpy2-2.3.5 → assertpy2-2.3.6}/tests/test_extracting.py +0 -28
- assertpy2-2.3.6/tests/test_fail.py +17 -0
- {assertpy2-2.3.5 → assertpy2-2.3.6}/tests/test_file.py +0 -28
- assertpy2-2.3.6/tests/test_in.py +47 -0
- {assertpy2-2.3.5 → assertpy2-2.3.6}/tests/test_list.py +0 -28
- {assertpy2-2.3.5 → assertpy2-2.3.6}/tests/test_namedtuple.py +0 -28
- assertpy2-2.3.6/tests/test_none.py +29 -0
- assertpy2-2.3.6/tests/test_not.py +150 -0
- {assertpy2-2.3.5 → assertpy2-2.3.6}/tests/test_numbers.py +0 -28
- assertpy2-2.3.6/tests/test_pipeline.py +173 -0
- {assertpy2-2.3.5 → assertpy2-2.3.6}/tests/test_readme.py +0 -28
- assertpy2-2.3.6/tests/test_same_as.py +44 -0
- {assertpy2-2.3.5 → assertpy2-2.3.6}/tests/test_snapshots.py +0 -28
- {assertpy2-2.3.5 → assertpy2-2.3.6}/tests/test_soft.py +0 -28
- {assertpy2-2.3.5 → assertpy2-2.3.6}/tests/test_soft_fail.py +0 -28
- {assertpy2-2.3.5 → assertpy2-2.3.6}/tests/test_string.py +0 -29
- assertpy2-2.3.6/tests/test_traceback.py +36 -0
- {assertpy2-2.3.5 → assertpy2-2.3.6}/tests/test_type.py +0 -28
- assertpy2-2.3.6/tests/test_warn.py +108 -0
- {assertpy2-2.3.5 → assertpy2-2.3.6}/uv.lock +1 -1
- assertpy2-2.3.5/CONTRIBUTING.md +0 -22
- assertpy2-2.3.5/tests/test_bool.py +0 -63
- assertpy2-2.3.5/tests/test_custom_list.py +0 -80
- assertpy2-2.3.5/tests/test_description.py +0 -63
- assertpy2-2.3.5/tests/test_fail.py +0 -45
- assertpy2-2.3.5/tests/test_in.py +0 -75
- assertpy2-2.3.5/tests/test_none.py +0 -57
- assertpy2-2.3.5/tests/test_same_as.py +0 -72
- assertpy2-2.3.5/tests/test_traceback.py +0 -64
- assertpy2-2.3.5/tests/test_warn.py +0 -136
- {assertpy2-2.3.5 → assertpy2-2.3.6}/.codecov.yml +0 -0
- {assertpy2-2.3.5 → assertpy2-2.3.6}/.github/dependabot.yml +0 -0
- {assertpy2-2.3.5 → assertpy2-2.3.6}/.github/workflows/ci.yml +0 -0
- {assertpy2-2.3.5 → assertpy2-2.3.6}/.github/workflows/codeql.yml +0 -0
- {assertpy2-2.3.5 → assertpy2-2.3.6}/.github/workflows/publish.yml +0 -0
- {assertpy2-2.3.5 → assertpy2-2.3.6}/.github/workflows/scorecard.yml +0 -0
- {assertpy2-2.3.5 → assertpy2-2.3.6}/.gitignore +0 -0
- {assertpy2-2.3.5 → assertpy2-2.3.6}/LICENSE +0 -0
- {assertpy2-2.3.5 → assertpy2-2.3.6}/SECURITY.md +0 -0
- {assertpy2-2.3.5 → assertpy2-2.3.6}/assertpy2/_mixin_base.py +0 -0
- {assertpy2-2.3.5 → assertpy2-2.3.6}/assertpy2/async_assertions.py +0 -0
- {assertpy2-2.3.5 → assertpy2-2.3.6}/assertpy2/behave_matchers.py +0 -0
- {assertpy2-2.3.5 → assertpy2-2.3.6}/assertpy2/errors.py +0 -0
- {assertpy2-2.3.5 → assertpy2-2.3.6}/assertpy2/matchers.py +0 -0
- {assertpy2-2.3.5 → assertpy2-2.3.6}/assertpy2/py.typed +0 -0
- {assertpy2-2.3.5 → assertpy2-2.3.6}/assertpy2/pytest_plugin.py +0 -0
- {assertpy2-2.3.5 → assertpy2-2.3.6}/docs/logo-dark.svg +0 -0
- {assertpy2-2.3.5 → assertpy2-2.3.6}/docs/logo.svg +0 -0
- {assertpy2-2.3.5 → assertpy2-2.3.6}/tests/test_async.py +0 -0
- {assertpy2-2.3.5 → assertpy2-2.3.6}/tests/test_behave_matchers.py +0 -0
- {assertpy2-2.3.5 → assertpy2-2.3.6}/tests/test_errors.py +0 -0
- {assertpy2-2.3.5 → assertpy2-2.3.6}/tests/test_matcher_registry.py +0 -0
- {assertpy2-2.3.5 → assertpy2-2.3.6}/tests/test_matchers.py +0 -0
- {assertpy2-2.3.5 → assertpy2-2.3.6}/tests/test_matchers_phase3.py +0 -0
- {assertpy2-2.3.5 → assertpy2-2.3.6}/tests/test_overloads.py +0 -0
- {assertpy2-2.3.5 → assertpy2-2.3.6}/tests/test_phase2.py +0 -0
- {assertpy2-2.3.5 → assertpy2-2.3.6}/tests/test_pytest_plugin.py +0 -0
- {assertpy2-2.3.5 → assertpy2-2.3.6}/tests/test_regex_groups.py +0 -0
- {assertpy2-2.3.5 → assertpy2-2.3.6}/tests/test_structural.py +0 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
Contributions of docs, tests, or code are welcome.
|
|
4
|
+
|
|
5
|
+
## Workflow
|
|
6
|
+
|
|
7
|
+
1. Fork the repo
|
|
8
|
+
2. Clone your fork (`git clone <your_fork_url>`)
|
|
9
|
+
3. Create a branch (`git checkout -b my_branch`)
|
|
10
|
+
4. Install dependencies: `uv sync`
|
|
11
|
+
5. Make your changes
|
|
12
|
+
6. Run the [verification pipeline](#verification-pipeline) and fix any issues
|
|
13
|
+
7. Commit using [Conventional Commits](#commit-style)
|
|
14
|
+
8. Push your branch (`git push origin my_branch`)
|
|
15
|
+
9. Open a [Pull Request](http://github.com/Solganis/assertpy2/pulls)
|
|
16
|
+
|
|
17
|
+
Read more about how pulls work on GitHub's [About pull requests](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-requests) page.
|
|
18
|
+
|
|
19
|
+
## Requirements
|
|
20
|
+
|
|
21
|
+
- Python 3.10+
|
|
22
|
+
- [uv](https://docs.astral.sh/uv/) as the package manager
|
|
23
|
+
|
|
24
|
+
## Verification pipeline
|
|
25
|
+
|
|
26
|
+
Run all checks before submitting a PR. Every step must pass.
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
# lint
|
|
30
|
+
uv run ruff check assertpy2/ tests/
|
|
31
|
+
|
|
32
|
+
# format
|
|
33
|
+
uv run ruff format --check assertpy2/ tests/
|
|
34
|
+
|
|
35
|
+
# type check
|
|
36
|
+
uv run ty check
|
|
37
|
+
|
|
38
|
+
# tests with coverage (must be 100%)
|
|
39
|
+
uv run pytest tests/ -v --cov=assertpy2 --cov-report=term-missing
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
CI requires 100% code coverage.
|
|
43
|
+
|
|
44
|
+
## Commit style
|
|
45
|
+
|
|
46
|
+
Use [Conventional Commits](https://www.conventionalcommits.org/): `feat:`, `fix:`, `refactor:`, `test:`, `docs:`, `chore:`, etc.
|
|
47
|
+
|
|
48
|
+
## Tests
|
|
49
|
+
|
|
50
|
+
Write tests for every new feature or bug fix. Use `assertpy2` assertions in tests.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: assertpy2
|
|
3
|
-
Version: 2.3.
|
|
3
|
+
Version: 2.3.6
|
|
4
4
|
Summary: Fluent assertion library for Python with composable matchers, structural matching, and full type safety
|
|
5
5
|
Project-URL: Homepage, https://github.com/Solganis/assertpy2
|
|
6
6
|
Project-URL: Repository, https://github.com/Solganis/assertpy2
|
|
@@ -162,6 +162,8 @@ assert_that(items).is_type_of(list).is_length(3).contains("admin")
|
|
|
162
162
|
|
|
163
163
|
- [**Composable matchers**](docs/api.md#composable-matchers): `match.greater_than(5)`, `match.is_uuid()`, combine with `&`, `|`, `~`. Also work with plain `assert ==`.
|
|
164
164
|
- [**Structural matching**](docs/api.md#structural-matching): `matches_structure()` for declarative dict/API response validation.
|
|
165
|
+
- [**Universal negation**](docs/api.md#universal-negation): `.not_` inverts any assertion without dedicated `is_not_*` methods.
|
|
166
|
+
- [**Collection pipeline**](docs/api.md#collection-pipeline): `filtered_on()`, `mapped()`, `flat_mapped()`, `first()`, `last()`, `element()`, `single()` for transforming collections before assertions.
|
|
165
167
|
- [**Async assertions**](docs/api.md#async-assertions): `eventually()` with polling/retry for async and eventual consistency testing.
|
|
166
168
|
- [**Structured errors**](docs/api.md#structured-errors): `AssertionFailure` with `.actual`, `.expected`, `.diff` attributes, pytest plugin with rich diff output.
|
|
167
169
|
- **Typed overloads**: `assert_that()` returns type-specific Protocols, IDE shows only relevant methods per type.
|
|
@@ -318,6 +320,38 @@ DiffResult(kind='dict', entries=[DiffEntry(path='b', actual=2, expected=99)])
|
|
|
318
320
|
|
|
319
321
|
## More features
|
|
320
322
|
|
|
323
|
+
### Universal negation
|
|
324
|
+
|
|
325
|
+
Invert any assertion with `.not_`:
|
|
326
|
+
|
|
327
|
+
```py
|
|
328
|
+
assert_that(5).not_.is_none()
|
|
329
|
+
assert_that("abc123").not_.is_alpha()
|
|
330
|
+
assert_that([3, 1, 2]).not_.is_sorted()
|
|
331
|
+
assert_that(value).described_as("check").not_.is_none().is_positive()
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
Works with soft assertions and warn mode.
|
|
335
|
+
|
|
336
|
+
### Collection pipeline
|
|
337
|
+
|
|
338
|
+
Transform collections before asserting:
|
|
339
|
+
|
|
340
|
+
```py
|
|
341
|
+
orders = [Order("DONE", 100), Order("FAILED", 50), Order("DONE", 200)]
|
|
342
|
+
|
|
343
|
+
assert_that(orders).filtered_on(lambda o: o.status == "FAILED").is_length(1)
|
|
344
|
+
assert_that(orders).mapped(lambda o: o.total).contains(100, 200)
|
|
345
|
+
assert_that(orders).first().has_status("DONE")
|
|
346
|
+
assert_that(orders).element(1).has_status("FAILED")
|
|
347
|
+
assert_that([42]).single().is_equal_to(42)
|
|
348
|
+
|
|
349
|
+
# chaining pipeline steps
|
|
350
|
+
assert_that(items).filtered_on(match.is_positive()).mapped(str).contains("1")
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
Available methods: `filtered_on()`, `mapped()`, `flat_mapped()`, `first()`, `last()`, `element()`, `single()`.
|
|
354
|
+
|
|
321
355
|
### Dict comparison with ignore/include
|
|
322
356
|
|
|
323
357
|
```py
|
|
@@ -492,19 +526,3 @@ See the [comparison table](#comparison) above for feature differences with other
|
|
|
492
526
|
Contributions are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md) for details.
|
|
493
527
|
|
|
494
528
|
|
|
495
|
-
## License
|
|
496
|
-
|
|
497
|
-
All files are licensed under the BSD 3-Clause License as follows:
|
|
498
|
-
|
|
499
|
-
> Copyright (c) 2015-2019, Activision Publishing, Inc.
|
|
500
|
-
> All rights reserved.
|
|
501
|
-
>
|
|
502
|
-
> Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
|
503
|
-
>
|
|
504
|
-
> 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
|
505
|
-
>
|
|
506
|
-
> 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
|
507
|
-
>
|
|
508
|
-
> 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
|
509
|
-
>
|
|
510
|
-
> THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -127,6 +127,8 @@ assert_that(items).is_type_of(list).is_length(3).contains("admin")
|
|
|
127
127
|
|
|
128
128
|
- [**Composable matchers**](docs/api.md#composable-matchers): `match.greater_than(5)`, `match.is_uuid()`, combine with `&`, `|`, `~`. Also work with plain `assert ==`.
|
|
129
129
|
- [**Structural matching**](docs/api.md#structural-matching): `matches_structure()` for declarative dict/API response validation.
|
|
130
|
+
- [**Universal negation**](docs/api.md#universal-negation): `.not_` inverts any assertion without dedicated `is_not_*` methods.
|
|
131
|
+
- [**Collection pipeline**](docs/api.md#collection-pipeline): `filtered_on()`, `mapped()`, `flat_mapped()`, `first()`, `last()`, `element()`, `single()` for transforming collections before assertions.
|
|
130
132
|
- [**Async assertions**](docs/api.md#async-assertions): `eventually()` with polling/retry for async and eventual consistency testing.
|
|
131
133
|
- [**Structured errors**](docs/api.md#structured-errors): `AssertionFailure` with `.actual`, `.expected`, `.diff` attributes, pytest plugin with rich diff output.
|
|
132
134
|
- **Typed overloads**: `assert_that()` returns type-specific Protocols, IDE shows only relevant methods per type.
|
|
@@ -283,6 +285,38 @@ DiffResult(kind='dict', entries=[DiffEntry(path='b', actual=2, expected=99)])
|
|
|
283
285
|
|
|
284
286
|
## More features
|
|
285
287
|
|
|
288
|
+
### Universal negation
|
|
289
|
+
|
|
290
|
+
Invert any assertion with `.not_`:
|
|
291
|
+
|
|
292
|
+
```py
|
|
293
|
+
assert_that(5).not_.is_none()
|
|
294
|
+
assert_that("abc123").not_.is_alpha()
|
|
295
|
+
assert_that([3, 1, 2]).not_.is_sorted()
|
|
296
|
+
assert_that(value).described_as("check").not_.is_none().is_positive()
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
Works with soft assertions and warn mode.
|
|
300
|
+
|
|
301
|
+
### Collection pipeline
|
|
302
|
+
|
|
303
|
+
Transform collections before asserting:
|
|
304
|
+
|
|
305
|
+
```py
|
|
306
|
+
orders = [Order("DONE", 100), Order("FAILED", 50), Order("DONE", 200)]
|
|
307
|
+
|
|
308
|
+
assert_that(orders).filtered_on(lambda o: o.status == "FAILED").is_length(1)
|
|
309
|
+
assert_that(orders).mapped(lambda o: o.total).contains(100, 200)
|
|
310
|
+
assert_that(orders).first().has_status("DONE")
|
|
311
|
+
assert_that(orders).element(1).has_status("FAILED")
|
|
312
|
+
assert_that([42]).single().is_equal_to(42)
|
|
313
|
+
|
|
314
|
+
# chaining pipeline steps
|
|
315
|
+
assert_that(items).filtered_on(match.is_positive()).mapped(str).contains("1")
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
Available methods: `filtered_on()`, `mapped()`, `flat_mapped()`, `first()`, `last()`, `element()`, `single()`.
|
|
319
|
+
|
|
286
320
|
### Dict comparison with ignore/include
|
|
287
321
|
|
|
288
322
|
```py
|
|
@@ -457,19 +491,3 @@ See the [comparison table](#comparison) above for feature differences with other
|
|
|
457
491
|
Contributions are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md) for details.
|
|
458
492
|
|
|
459
493
|
|
|
460
|
-
## License
|
|
461
|
-
|
|
462
|
-
All files are licensed under the BSD 3-Clause License as follows:
|
|
463
|
-
|
|
464
|
-
> Copyright (c) 2015-2019, Activision Publishing, Inc.
|
|
465
|
-
> All rights reserved.
|
|
466
|
-
>
|
|
467
|
-
> Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
|
468
|
-
>
|
|
469
|
-
> 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
|
470
|
-
>
|
|
471
|
-
> 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
|
472
|
-
>
|
|
473
|
-
> 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
|
474
|
-
>
|
|
475
|
-
> THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from .assertpy import (
|
|
2
|
+
NegatedBuilder,
|
|
2
3
|
WarningLoggingAdapter,
|
|
3
4
|
__version__,
|
|
4
5
|
add_extension,
|
|
@@ -20,6 +21,7 @@ __all__ = [
|
|
|
20
21
|
"DiffEntry",
|
|
21
22
|
"DiffResult",
|
|
22
23
|
"Matcher",
|
|
24
|
+
"NegatedBuilder",
|
|
23
25
|
"WarningLoggingAdapter",
|
|
24
26
|
"__version__",
|
|
25
27
|
"add_extension",
|
|
@@ -8,6 +8,7 @@ if TYPE_CHECKING:
|
|
|
8
8
|
|
|
9
9
|
from typing_extensions import Self
|
|
10
10
|
|
|
11
|
+
from .assertpy import NegatedBuilder
|
|
11
12
|
from .async_assertions import AsyncAssertionBuilder
|
|
12
13
|
from .matchers import Matcher
|
|
13
14
|
|
|
@@ -35,6 +36,9 @@ if TYPE_CHECKING:
|
|
|
35
36
|
def is_not_in(self, *items: object) -> Self: ...
|
|
36
37
|
# SnapshotMixin
|
|
37
38
|
def snapshot(self, id: str | None = ..., path: str = ...) -> Self: ...
|
|
39
|
+
# NegatedBuilder
|
|
40
|
+
@property
|
|
41
|
+
def not_(self) -> NegatedBuilder: ...
|
|
38
42
|
|
|
39
43
|
class _StringAssertion(_CoreAssertion, Protocol):
|
|
40
44
|
"""Assertions available for ``str`` values."""
|
|
@@ -129,6 +133,14 @@ if TYPE_CHECKING:
|
|
|
129
133
|
def any_satisfy(self, matcher: Matcher | Callable[..., bool]) -> Self: ...
|
|
130
134
|
def all_satisfy(self, matcher: Matcher | Callable[..., bool]) -> Self: ...
|
|
131
135
|
def none_satisfy(self, matcher: Matcher | Callable[..., bool]) -> Self: ...
|
|
136
|
+
# CollectionMixin - pipeline
|
|
137
|
+
def filtered_on(self, predicate: Callable[..., bool]) -> Self: ...
|
|
138
|
+
def mapped(self, func: Callable[..., object]) -> Self: ...
|
|
139
|
+
def flat_mapped(self, func: Callable[..., object]) -> Self: ...
|
|
140
|
+
def first(self) -> Self: ...
|
|
141
|
+
def last(self) -> Self: ...
|
|
142
|
+
def element(self, index: int) -> Self: ...
|
|
143
|
+
def single(self) -> Self: ...
|
|
132
144
|
|
|
133
145
|
class _DictAssertion(_CoreAssertion, Protocol):
|
|
134
146
|
"""Assertions available for ``dict`` values."""
|
|
@@ -1,31 +1,3 @@
|
|
|
1
|
-
# Copyright (c) 2015-2019, Activision Publishing, Inc.
|
|
2
|
-
# All rights reserved.
|
|
3
|
-
#
|
|
4
|
-
# Redistribution and use in source and binary forms, with or without modification,
|
|
5
|
-
# are permitted provided that the following conditions are met:
|
|
6
|
-
#
|
|
7
|
-
# 1. Redistributions of source code must retain the above copyright notice, this
|
|
8
|
-
# list of conditions and the following disclaimer.
|
|
9
|
-
#
|
|
10
|
-
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
11
|
-
# this list of conditions and the following disclaimer in the documentation
|
|
12
|
-
# and/or other materials provided with the distribution.
|
|
13
|
-
#
|
|
14
|
-
# 3. Neither the name of the copyright holder nor the names of its contributors
|
|
15
|
-
# may be used to endorse or promote products derived from this software without
|
|
16
|
-
# specific prior written permission.
|
|
17
|
-
#
|
|
18
|
-
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
19
|
-
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
20
|
-
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
21
|
-
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
|
22
|
-
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
23
|
-
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
24
|
-
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
|
25
|
-
# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
26
|
-
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
27
|
-
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
28
|
-
|
|
29
1
|
"""Assertion library for python unit testing with a fluent API"""
|
|
30
2
|
|
|
31
3
|
from __future__ import annotations
|
|
@@ -37,13 +9,12 @@ import logging
|
|
|
37
9
|
import os
|
|
38
10
|
import sys
|
|
39
11
|
import types
|
|
40
|
-
from collections.abc import Iterator
|
|
12
|
+
from collections.abc import Callable, Iterator
|
|
41
13
|
from typing import TYPE_CHECKING, overload
|
|
42
14
|
|
|
43
15
|
if TYPE_CHECKING:
|
|
44
16
|
import datetime
|
|
45
17
|
import pathlib
|
|
46
|
-
from collections.abc import Callable
|
|
47
18
|
|
|
48
19
|
from typing_extensions import Self
|
|
49
20
|
|
|
@@ -73,7 +44,7 @@ from .numeric import NumericMixin
|
|
|
73
44
|
from .snapshot import SnapshotMixin
|
|
74
45
|
from .string import StringMixin
|
|
75
46
|
|
|
76
|
-
__version__ = "2.3.
|
|
47
|
+
__version__ = "2.3.6"
|
|
77
48
|
|
|
78
49
|
__tracebackhide__ = True # clean tracebacks via py.test integration
|
|
79
50
|
contextlib.__tracebackhide__ = True # ty: ignore[unresolved-attribute] # pytest monkey-patch
|
|
@@ -436,6 +407,65 @@ _logger.addHandler(_handler)
|
|
|
436
407
|
_default_logger = WarningLoggingAdapter(_logger, None)
|
|
437
408
|
|
|
438
409
|
|
|
410
|
+
class NegatedBuilder:
|
|
411
|
+
"""Proxy that inverts the next assertion. Created by ``assert_that(val).not_``."""
|
|
412
|
+
|
|
413
|
+
def __init__(self, builder: AssertionBuilder) -> None:
|
|
414
|
+
self._builder = builder
|
|
415
|
+
|
|
416
|
+
def __getattr__(self, name: str) -> object:
|
|
417
|
+
attr = getattr(self._builder, name)
|
|
418
|
+
if not callable(attr):
|
|
419
|
+
return attr
|
|
420
|
+
|
|
421
|
+
def _negated(*args: object, **kwargs: object) -> AssertionBuilder:
|
|
422
|
+
if self._builder.kind == "soft":
|
|
423
|
+
return self._negated_soft(name, attr, *args, **kwargs)
|
|
424
|
+
if self._builder.kind == "warn":
|
|
425
|
+
return self._negated_warn(name, attr, *args, **kwargs)
|
|
426
|
+
return self._negated_strict(name, attr, *args, **kwargs)
|
|
427
|
+
|
|
428
|
+
return _negated
|
|
429
|
+
|
|
430
|
+
def _make_msg(self, name: str) -> str:
|
|
431
|
+
desc = f"[{self._builder.description}] " if self._builder.description else ""
|
|
432
|
+
return f"{desc}Expected <{self._builder.val}> to NOT satisfy: {name}()"
|
|
433
|
+
|
|
434
|
+
def _negated_strict(
|
|
435
|
+
self, name: str, attr: Callable[..., object], *args: object, **kwargs: object
|
|
436
|
+
) -> AssertionBuilder:
|
|
437
|
+
try:
|
|
438
|
+
attr(*args, **kwargs)
|
|
439
|
+
except (AssertionError, AssertionFailure):
|
|
440
|
+
return self._builder
|
|
441
|
+
raise AssertionError(self._make_msg(name))
|
|
442
|
+
|
|
443
|
+
def _negated_soft(
|
|
444
|
+
self, name: str, attr: Callable[..., object], *args: object, **kwargs: object
|
|
445
|
+
) -> AssertionBuilder:
|
|
446
|
+
err_list = _soft_err.get()
|
|
447
|
+
before = len(err_list)
|
|
448
|
+
attr(*args, **kwargs)
|
|
449
|
+
if len(err_list) > before:
|
|
450
|
+
del err_list[before:]
|
|
451
|
+
return self._builder
|
|
452
|
+
err_list.append(self._make_msg(name))
|
|
453
|
+
return self._builder
|
|
454
|
+
|
|
455
|
+
def _negated_warn(
|
|
456
|
+
self, name: str, attr: Callable[..., object], *args: object, **kwargs: object
|
|
457
|
+
) -> AssertionBuilder:
|
|
458
|
+
self._builder.kind = None
|
|
459
|
+
try:
|
|
460
|
+
attr(*args, **kwargs)
|
|
461
|
+
except (AssertionError, AssertionFailure):
|
|
462
|
+
return self._builder
|
|
463
|
+
finally:
|
|
464
|
+
self._builder.kind = "warn"
|
|
465
|
+
self._builder.logger.warning(self._make_msg(name))
|
|
466
|
+
return self._builder
|
|
467
|
+
|
|
468
|
+
|
|
439
469
|
class AssertionBuilder(
|
|
440
470
|
StringMixin,
|
|
441
471
|
SnapshotMixin,
|
|
@@ -474,6 +504,11 @@ class AssertionBuilder(
|
|
|
474
504
|
self.logger = logger if logger else _default_logger
|
|
475
505
|
self._not_expected = False
|
|
476
506
|
|
|
507
|
+
@property
|
|
508
|
+
def not_(self) -> NegatedBuilder:
|
|
509
|
+
"""Invert the next assertion in the chain."""
|
|
510
|
+
return NegatedBuilder(self)
|
|
511
|
+
|
|
477
512
|
def builder(self, val, description="", kind=None, expected=None, logger=None):
|
|
478
513
|
"""Helper to build a new :class:`AssertionBuilder` instance. Use this only if not chaining to ``self``.
|
|
479
514
|
|
|
@@ -1,31 +1,3 @@
|
|
|
1
|
-
# Copyright (c) 2015-2019, Activision Publishing, Inc.
|
|
2
|
-
# All rights reserved.
|
|
3
|
-
#
|
|
4
|
-
# Redistribution and use in source and binary forms, with or without modification,
|
|
5
|
-
# are permitted provided that the following conditions are met:
|
|
6
|
-
#
|
|
7
|
-
# 1. Redistributions of source code must retain the above copyright notice, this
|
|
8
|
-
# list of conditions and the following disclaimer.
|
|
9
|
-
#
|
|
10
|
-
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
11
|
-
# this list of conditions and the following disclaimer in the documentation
|
|
12
|
-
# and/or other materials provided with the distribution.
|
|
13
|
-
#
|
|
14
|
-
# 3. Neither the name of the copyright holder nor the names of its contributors
|
|
15
|
-
# may be used to endorse or promote products derived from this software without
|
|
16
|
-
# specific prior written permission.
|
|
17
|
-
#
|
|
18
|
-
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
19
|
-
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
20
|
-
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
21
|
-
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
|
22
|
-
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
23
|
-
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
24
|
-
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
|
25
|
-
# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
26
|
-
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
27
|
-
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
28
|
-
|
|
29
1
|
from __future__ import annotations
|
|
30
2
|
|
|
31
3
|
import collections.abc
|
|
@@ -1,37 +1,11 @@
|
|
|
1
|
-
# Copyright (c) 2015-2019, Activision Publishing, Inc.
|
|
2
|
-
# All rights reserved.
|
|
3
|
-
#
|
|
4
|
-
# Redistribution and use in source and binary forms, with or without modification,
|
|
5
|
-
# are permitted provided that the following conditions are met:
|
|
6
|
-
#
|
|
7
|
-
# 1. Redistributions of source code must retain the above copyright notice, this
|
|
8
|
-
# list of conditions and the following disclaimer.
|
|
9
|
-
#
|
|
10
|
-
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
11
|
-
# this list of conditions and the following disclaimer in the documentation
|
|
12
|
-
# and/or other materials provided with the distribution.
|
|
13
|
-
#
|
|
14
|
-
# 3. Neither the name of the copyright holder nor the names of its contributors
|
|
15
|
-
# may be used to endorse or promote products derived from this software without
|
|
16
|
-
# specific prior written permission.
|
|
17
|
-
#
|
|
18
|
-
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
19
|
-
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
20
|
-
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
21
|
-
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
|
22
|
-
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
23
|
-
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
24
|
-
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
|
25
|
-
# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
26
|
-
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
27
|
-
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
28
|
-
|
|
29
1
|
from __future__ import annotations
|
|
30
2
|
|
|
31
3
|
import collections
|
|
32
|
-
from
|
|
4
|
+
from collections.abc import Callable, Iterable
|
|
5
|
+
from typing import TYPE_CHECKING, Any
|
|
33
6
|
|
|
34
7
|
from ._mixin_base import _MixinBase
|
|
8
|
+
from .matchers import BaseMatcher
|
|
35
9
|
|
|
36
10
|
if TYPE_CHECKING:
|
|
37
11
|
from typing_extensions import Self
|
|
@@ -205,3 +179,151 @@ class CollectionMixin(_MixinBase):
|
|
|
205
179
|
prev = x
|
|
206
180
|
|
|
207
181
|
return self
|
|
182
|
+
|
|
183
|
+
def filtered_on(self, predicate: Callable[[Any], bool]) -> Self:
|
|
184
|
+
"""Returns a new builder with elements matching the predicate.
|
|
185
|
+
|
|
186
|
+
Args:
|
|
187
|
+
predicate: callable or Matcher. If a Matcher, uses ``predicate.matches(item)``.
|
|
188
|
+
|
|
189
|
+
Examples:
|
|
190
|
+
Usage::
|
|
191
|
+
|
|
192
|
+
assert_that([1, -2, 3]).filtered_on(lambda x: x > 0).is_length(2)
|
|
193
|
+
assert_that(items).filtered_on(match.is_positive()).is_not_empty()
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
AssertionBuilder: returns a new instance with the filtered list as val
|
|
197
|
+
"""
|
|
198
|
+
if not isinstance(self.val, collections.abc.Iterable):
|
|
199
|
+
raise TypeError("val is not iterable")
|
|
200
|
+
if isinstance(predicate, BaseMatcher):
|
|
201
|
+
filtered = [item for item in self.val if predicate.matches(item)]
|
|
202
|
+
else:
|
|
203
|
+
filtered = [item for item in self.val if predicate(item)]
|
|
204
|
+
return self.builder(filtered, self.description, self.kind)
|
|
205
|
+
|
|
206
|
+
def mapped(self, func: Callable[[Any], Any]) -> Self:
|
|
207
|
+
"""Returns a new builder with each element transformed by func.
|
|
208
|
+
|
|
209
|
+
Args:
|
|
210
|
+
func: callable applied to each element.
|
|
211
|
+
|
|
212
|
+
Examples:
|
|
213
|
+
Usage::
|
|
214
|
+
|
|
215
|
+
assert_that(["a", "b"]).mapped(str.upper).contains("A")
|
|
216
|
+
|
|
217
|
+
Returns:
|
|
218
|
+
AssertionBuilder: returns a new instance with the mapped list as val
|
|
219
|
+
"""
|
|
220
|
+
if not isinstance(self.val, collections.abc.Iterable):
|
|
221
|
+
raise TypeError("val is not iterable")
|
|
222
|
+
return self.builder([func(item) for item in self.val], self.description, self.kind)
|
|
223
|
+
|
|
224
|
+
def flat_mapped(self, func: Callable[[Any], Iterable[Any]]) -> Self:
|
|
225
|
+
"""Returns a new builder with each element expanded and flattened by func.
|
|
226
|
+
|
|
227
|
+
Args:
|
|
228
|
+
func: callable returning an iterable for each element.
|
|
229
|
+
|
|
230
|
+
Examples:
|
|
231
|
+
Usage::
|
|
232
|
+
|
|
233
|
+
assert_that(["ab", "cd"]).flat_mapped(list).contains("a", "c")
|
|
234
|
+
|
|
235
|
+
Returns:
|
|
236
|
+
AssertionBuilder: returns a new instance with the flattened list as val
|
|
237
|
+
"""
|
|
238
|
+
if not isinstance(self.val, collections.abc.Iterable):
|
|
239
|
+
raise TypeError("val is not iterable")
|
|
240
|
+
return self.builder([x for item in self.val for x in func(item)], self.description, self.kind)
|
|
241
|
+
|
|
242
|
+
def first(self) -> Self:
|
|
243
|
+
"""Returns a new builder with the first element of val.
|
|
244
|
+
|
|
245
|
+
Examples:
|
|
246
|
+
Usage::
|
|
247
|
+
|
|
248
|
+
assert_that([10, 20, 30]).first().is_equal_to(10)
|
|
249
|
+
|
|
250
|
+
Returns:
|
|
251
|
+
AssertionBuilder: returns a new instance with the first element as val
|
|
252
|
+
|
|
253
|
+
Raises:
|
|
254
|
+
ValueError: if val is empty
|
|
255
|
+
"""
|
|
256
|
+
if not isinstance(self.val, collections.abc.Iterable):
|
|
257
|
+
raise TypeError("val is not iterable")
|
|
258
|
+
items = list(self.val)
|
|
259
|
+
if not items:
|
|
260
|
+
raise ValueError("Expected non-empty iterable, but was empty.")
|
|
261
|
+
return self.builder(items[0], self.description, self.kind)
|
|
262
|
+
|
|
263
|
+
def last(self) -> Self:
|
|
264
|
+
"""Returns a new builder with the last element of val.
|
|
265
|
+
|
|
266
|
+
Examples:
|
|
267
|
+
Usage::
|
|
268
|
+
|
|
269
|
+
assert_that([10, 20, 30]).last().is_equal_to(30)
|
|
270
|
+
|
|
271
|
+
Returns:
|
|
272
|
+
AssertionBuilder: returns a new instance with the last element as val
|
|
273
|
+
|
|
274
|
+
Raises:
|
|
275
|
+
ValueError: if val is empty
|
|
276
|
+
"""
|
|
277
|
+
if not isinstance(self.val, collections.abc.Iterable):
|
|
278
|
+
raise TypeError("val is not iterable")
|
|
279
|
+
items = list(self.val)
|
|
280
|
+
if not items:
|
|
281
|
+
raise ValueError("Expected non-empty iterable, but was empty.")
|
|
282
|
+
return self.builder(items[-1], self.description, self.kind)
|
|
283
|
+
|
|
284
|
+
def element(self, index: int) -> Self:
|
|
285
|
+
"""Returns a new builder with the element at the given index.
|
|
286
|
+
|
|
287
|
+
Args:
|
|
288
|
+
index: zero-based index.
|
|
289
|
+
|
|
290
|
+
Examples:
|
|
291
|
+
Usage::
|
|
292
|
+
|
|
293
|
+
assert_that([10, 20, 30]).element(1).is_equal_to(20)
|
|
294
|
+
|
|
295
|
+
Returns:
|
|
296
|
+
AssertionBuilder: returns a new instance with the selected element as val
|
|
297
|
+
|
|
298
|
+
Raises:
|
|
299
|
+
IndexError: if index is out of range
|
|
300
|
+
"""
|
|
301
|
+
if not isinstance(self.val, collections.abc.Iterable):
|
|
302
|
+
raise TypeError("val is not iterable")
|
|
303
|
+
items = list(self.val)
|
|
304
|
+
if index < 0 or index >= len(items):
|
|
305
|
+
raise IndexError(f"Expected index {index} to be in range [0, {len(items)}), but was out of range.")
|
|
306
|
+
return self.builder(items[index], self.description, self.kind)
|
|
307
|
+
|
|
308
|
+
def single(self) -> Self:
|
|
309
|
+
"""Returns a new builder with the only element of val.
|
|
310
|
+
|
|
311
|
+
Examples:
|
|
312
|
+
Usage::
|
|
313
|
+
|
|
314
|
+
assert_that([42]).single().is_equal_to(42)
|
|
315
|
+
|
|
316
|
+
Returns:
|
|
317
|
+
AssertionBuilder: returns a new instance with the single element as val
|
|
318
|
+
|
|
319
|
+
Raises:
|
|
320
|
+
ValueError: if val is empty or has more than one element
|
|
321
|
+
"""
|
|
322
|
+
if not isinstance(self.val, collections.abc.Iterable):
|
|
323
|
+
raise TypeError("val is not iterable")
|
|
324
|
+
items = list(self.val)
|
|
325
|
+
if not items:
|
|
326
|
+
raise ValueError("Expected iterable with single element, but was empty.")
|
|
327
|
+
if len(items) > 1:
|
|
328
|
+
raise ValueError(f"Expected iterable with single element, but had {len(items)} elements.")
|
|
329
|
+
return self.builder(items[0], self.description, self.kind)
|
|
@@ -1,32 +1,3 @@
|
|
|
1
|
-
# Copyright (c) 2015-2019, Activision Publishing, Inc.
|
|
2
|
-
# All rights reserved.
|
|
3
|
-
#
|
|
4
|
-
# Redistribution and use in source and binary forms, with or without modification,
|
|
5
|
-
# are permitted provided that the following conditions are met:
|
|
6
|
-
#
|
|
7
|
-
# 1. Redistributions of source code must retain the above copyright notice, this
|
|
8
|
-
# list of conditions and the following disclaimer.
|
|
9
|
-
#
|
|
10
|
-
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
11
|
-
# this list of conditions and the following disclaimer in the documentation
|
|
12
|
-
# and/or other materials provided with the distribution.
|
|
13
|
-
#
|
|
14
|
-
# 3. Neither the name of the copyright holder nor the names of its contributors
|
|
15
|
-
# may be used to endorse or promote products derived from this software without
|
|
16
|
-
# specific prior written permission.
|
|
17
|
-
#
|
|
18
|
-
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
19
|
-
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
20
|
-
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
21
|
-
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
|
22
|
-
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
23
|
-
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
24
|
-
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
|
25
|
-
# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
26
|
-
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
27
|
-
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
28
|
-
|
|
29
|
-
|
|
30
1
|
from __future__ import annotations
|
|
31
2
|
|
|
32
3
|
from typing import TYPE_CHECKING
|
|
@@ -1,31 +1,3 @@
|
|
|
1
|
-
# Copyright (c) 2015-2019, Activision Publishing, Inc.
|
|
2
|
-
# All rights reserved.
|
|
3
|
-
#
|
|
4
|
-
# Redistribution and use in source and binary forms, with or without modification,
|
|
5
|
-
# are permitted provided that the following conditions are met:
|
|
6
|
-
#
|
|
7
|
-
# 1. Redistributions of source code must retain the above copyright notice, this
|
|
8
|
-
# list of conditions and the following disclaimer.
|
|
9
|
-
#
|
|
10
|
-
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
11
|
-
# this list of conditions and the following disclaimer in the documentation
|
|
12
|
-
# and/or other materials provided with the distribution.
|
|
13
|
-
#
|
|
14
|
-
# 3. Neither the name of the copyright holder nor the names of its contributors
|
|
15
|
-
# may be used to endorse or promote products derived from this software without
|
|
16
|
-
# specific prior written permission.
|
|
17
|
-
#
|
|
18
|
-
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
19
|
-
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
20
|
-
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
21
|
-
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
|
22
|
-
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
23
|
-
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
24
|
-
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
|
25
|
-
# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
26
|
-
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
27
|
-
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
28
|
-
|
|
29
1
|
from __future__ import annotations
|
|
30
2
|
|
|
31
3
|
import datetime
|