assertpy2 2.24.0__tar.gz → 2.26.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.
- {assertpy2-2.24.0 → assertpy2-2.26.0}/CONTRIBUTING.md +10 -7
- {assertpy2-2.24.0 → assertpy2-2.26.0}/PKG-INFO +27 -17
- {assertpy2-2.24.0 → assertpy2-2.26.0}/README.md +24 -14
- {assertpy2-2.24.0 → assertpy2-2.26.0}/assertpy2/__init__.py +2 -1
- {assertpy2-2.24.0 → assertpy2-2.26.0}/assertpy2/_engine/_builder_check_typing.py +81 -63
- assertpy2-2.26.0/assertpy2/_engine/_capable_typing.py +729 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/assertpy2/_engine/_check_typing.py +1 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/assertpy2/_engine/_diff.py +49 -38
- {assertpy2-2.24.0 → assertpy2-2.26.0}/assertpy2/_engine/_equality.py +108 -3
- {assertpy2-2.24.0 → assertpy2-2.26.0}/assertpy2/_engine/_introspection.py +44 -7
- {assertpy2-2.24.0 → assertpy2-2.26.0}/assertpy2/_engine/_mixin_base.py +3 -1
- assertpy2-2.26.0/assertpy2/_engine/_negated_typing.py +1688 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/assertpy2/_engine/_poll_typing.py +151 -145
- {assertpy2-2.24.0 → assertpy2-2.26.0}/assertpy2/_engine/_typing.py +100 -8
- {assertpy2-2.24.0 → assertpy2-2.26.0}/assertpy2/_matcher_impls.py +97 -13
- {assertpy2-2.24.0 → assertpy2-2.26.0}/assertpy2/assertpy.py +260 -14
- {assertpy2-2.24.0 → assertpy2-2.26.0}/assertpy2/async_assertions.py +4 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/assertpy2/base.py +7 -3
- {assertpy2-2.24.0 → assertpy2-2.26.0}/assertpy2/dynamic.py +21 -2
- {assertpy2-2.24.0 → assertpy2-2.26.0}/assertpy2/errors.py +47 -21
- {assertpy2-2.24.0 → assertpy2-2.26.0}/assertpy2/exception.py +212 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/assertpy2/file.py +3 -3
- {assertpy2-2.24.0 → assertpy2-2.26.0}/assertpy2/helpers.py +178 -78
- {assertpy2-2.24.0 → assertpy2-2.26.0}/assertpy2/json_mixin.py +6 -6
- {assertpy2-2.24.0 → assertpy2-2.26.0}/assertpy2/numeric.py +17 -5
- {assertpy2-2.24.0 → assertpy2-2.26.0}/assertpy2/outcome.py +46 -1
- {assertpy2-2.24.0 → assertpy2-2.26.0}/assertpy2/pytest_plugin.py +132 -44
- {assertpy2-2.24.0 → assertpy2-2.26.0}/assertpy2/snapshot.py +12 -1
- {assertpy2-2.24.0 → assertpy2-2.26.0}/benchmarks/test_perf.py +31 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/docs/concepts/stability.md +35 -9
- {assertpy2-2.24.0 → assertpy2-2.26.0}/docs/extending/custom-assertions.md +51 -2
- {assertpy2-2.24.0 → assertpy2-2.26.0}/docs/extending/integrations.md +22 -8
- {assertpy2-2.24.0 → assertpy2-2.26.0}/docs/getting-started/migration.md +3 -3
- {assertpy2-2.24.0 → assertpy2-2.26.0}/docs/guides/assertions.md +11 -2
- {assertpy2-2.24.0 → assertpy2-2.26.0}/docs/guides/errors.md +83 -9
- {assertpy2-2.24.0 → assertpy2-2.26.0}/docs/guides/fluent.md +6 -2
- {assertpy2-2.24.0 → assertpy2-2.26.0}/docs/guides/matchers.md +14 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/docs/guides/testing.md +5 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/docs/recipes.md +29 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/docs/reference/entry-points.md +6 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/pyproject.toml +45 -15
- {assertpy2-2.24.0 → assertpy2-2.26.0}/scripts/generate_check_protocols.py +18 -2
- {assertpy2-2.24.0 → assertpy2-2.26.0}/scripts/generate_poll_protocols.py +377 -23
- assertpy2-2.26.0/scripts/regenerate.py +49 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/api_snapshot.json +691 -24
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/api_surface.py +30 -5
- assertpy2-2.26.0/tests/poll_parity_baseline.py +921 -0
- assertpy2-2.26.0/tests/poll_parity_cases.py +3331 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/pyright_baseline.py +2 -2
- assertpy2-2.26.0/tests/test_architecture_doc.py +107 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_async_predicates.py +38 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_capable_protocol.py +84 -6
- assertpy2-2.26.0/tests/test_complexity.py +122 -0
- assertpy2-2.26.0/tests/test_custom_dict.py +1010 -0
- assertpy2-2.26.0/tests/test_declared_floors.py +190 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_dict_compare.py +8 -5
- assertpy2-2.26.0/tests/test_docs_examples.py +334 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_equals.py +5 -5
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_expected_contract.py +90 -1
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_expected_exception.py +201 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_extensions.py +91 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_matchers.py +117 -9
- assertpy2-2.26.0/tests/test_message_elision.py +267 -0
- assertpy2-2.26.0/tests/test_negated_protocols.py +406 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_numbers.py +26 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_openapi_contract.py +23 -0
- assertpy2-2.26.0/tests/test_pin_coverage.py +500 -0
- assertpy2-2.26.0/tests/test_poll_parity.py +378 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_poll_protocols.py +2 -1
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_property_based.py +259 -36
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_public_surface.py +10 -2
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_pyright_baseline.py +10 -4
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_pytest_plugin.py +563 -62
- assertpy2-2.26.0/tests/test_requirement.py +673 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_rich_diff.py +4 -4
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_snapshots.py +74 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_typing.py +237 -2
- assertpy2-2.26.0/tests/test_typing_promises.py +67 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/typing_cases.py +38 -2
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/typing_harness.py +17 -8
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/typing_negative_baseline.py +38 -12
- assertpy2-2.26.0/tests/typing_promises.py +199 -0
- assertpy2-2.24.0/assertpy2/_engine/_capable_typing.py +0 -402
- assertpy2-2.24.0/tests/test_architecture_doc.py +0 -133
- assertpy2-2.24.0/tests/test_custom_dict.py +0 -231
- assertpy2-2.24.0/tests/test_docs_examples.py +0 -123
- assertpy2-2.24.0/tests/test_message_elision.py +0 -117
- {assertpy2-2.24.0 → assertpy2-2.26.0}/.gitignore +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/LICENSE +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/SECURITY.md +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/assertpy2/_clustering.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/assertpy2/_dangling.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/assertpy2/_engine/__init__.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/assertpy2/_engine/_compare.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/assertpy2/_engine/_compat.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/assertpy2/_engine/_contract.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/assertpy2/_engine/_membership.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/assertpy2/_engine/_operations.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/assertpy2/_engine/_ordering.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/assertpy2/_engine/_path.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/assertpy2/_engine/_require.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/assertpy2/_engine/_size.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/assertpy2/_engine/_text.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/assertpy2/_hints.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/assertpy2/_inline.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/assertpy2/_satisfies.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/assertpy2/_snapshot_codec.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/assertpy2/behave_matchers.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/assertpy2/bytes_mixin.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/assertpy2/collection.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/assertpy2/contains.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/assertpy2/dataframe.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/assertpy2/date.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/assertpy2/dict.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/assertpy2/extracting.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/assertpy2/http_mixin.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/assertpy2/matchers.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/assertpy2/py.typed +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/assertpy2/string.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/assertpy2/warning.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/docs/assets/diff-equal.png +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/docs/assets/diff-equal.svg +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/docs/assets/diff-gallery.png +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/docs/assets/diff-match.svg +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/docs/assets/diff-sequence.svg +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/docs/assets/diff-set.svg +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/docs/assets/diff-string.svg +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/docs/concepts/type-safety.md +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/docs/getting-started/comparison.md +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/docs/getting-started/quickstart.md +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/docs/guides/data.md +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/docs/index.md +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/docs/logo-dark.svg +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/docs/logo.svg +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/docs/reference/async.md +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/docs/reference/bytes.md +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/docs/reference/collections.md +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/docs/reference/containment.md +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/docs/reference/core.md +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/docs/reference/dataframes.md +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/docs/reference/dates.md +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/docs/reference/dicts.md +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/docs/reference/dynamic.md +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/docs/reference/errors.md +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/docs/reference/exceptions.md +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/docs/reference/extracting.md +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/docs/reference/files.md +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/docs/reference/http.md +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/docs/reference/json.md +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/docs/reference/matchers.md +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/docs/reference/numbers.md +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/docs/reference/overview.md +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/docs/reference/snapshots.md +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/docs/reference/strings.md +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/docs/reference/warnings.md +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/docs/stylesheets/extra.css +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/mkdocs.yml +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/mkdocs_hooks.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/scripts/mutation_report.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/conftest.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/docs_fixtures.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/group_compat.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_api_compatibility.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_api_vocabulary.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_async.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_attrs_support.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_behave_matchers.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_bool.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_boundary_cases.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_bytes.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_callable.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_chaining.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_check.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_check_protocols.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_class.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_clustering.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_collection.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_core.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_custom_list.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_dangling.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_dataframe.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_datetime.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_description.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_dict.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_docs_typing.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_dynamic.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_errors.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_evaluation_core.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_exception_context.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_expected_warning.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_extracting.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_fail.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_file.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_grouped_soft.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_hints.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_http.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_in.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_inline_record.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_inline_snapshot.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_iterable_cluster.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_json.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_list.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_match_result.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_matcher_parity.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_matcher_registry.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_namedtuple.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_none.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_not.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_operation_contract.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_optional_integration_contracts.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_overload_order.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_overloads.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_pipeline.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_property_attrs.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_protocol_parity.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_recursive_assertion.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_recursive_compare.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_recursive_compare_attrs.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_recursive_compare_config.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_recursive_compare_pydantic.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_refusals.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_regex_groups.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_same_as.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_satisfy.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_skip_guard.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_soft.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_soft_fail.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_string.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_structural.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_surface_conformance.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_sync_eventually.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_traceback.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_type.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_type_expression_failures.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_typing_claims.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_typing_completeness.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_typing_conformance.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_typing_from_a_wheel.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_typing_http.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_typing_integrations.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_typing_negative.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_vacuity_contract.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/test_warn.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/typing_http.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/typing_integrations.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.26.0}/tests/typing_integrations_baseline.py +0 -0
|
@@ -25,7 +25,7 @@ Read more about how pulls work on GitHub's [About pull requests](https://help.gi
|
|
|
25
25
|
|
|
26
26
|
## Before you change a public signature
|
|
27
27
|
|
|
28
|
-
Read [ARCHITECTURE.md](ARCHITECTURE.md).
|
|
28
|
+
Read [ARCHITECTURE.md](ARCHITECTURE.md). Five files under `assertpy2/_engine/` are generated, the public
|
|
29
29
|
surface is pinned by a recorded snapshot, and neither is discoverable from the files themselves.
|
|
30
30
|
|
|
31
31
|
## Verification pipeline
|
|
@@ -86,15 +86,16 @@ Run these after the main gate, not before. `uv run --group typecheck` adds the c
|
|
|
86
86
|
|
|
87
87
|
```bash
|
|
88
88
|
uv run --group typecheck mypy --strict --follow-imports=silent tests/test_typing.py
|
|
89
|
-
PYRIGHT_PYTHON_FORCE_VERSION=1.1.
|
|
89
|
+
PYRIGHT_PYTHON_FORCE_VERSION=1.1.414 uv run --group typecheck pyright --pythonversion 3.14 tests/test_typing.py
|
|
90
90
|
uv run --group typecheck pyrefly check tests/test_typing.py
|
|
91
91
|
uv run --group typecheck pytest tests/test_pyright_baseline.py
|
|
92
92
|
```
|
|
93
93
|
|
|
94
94
|
`PYRIGHT_PYTHON_FORCE_VERSION` picks the engine rather than the launcher. The `pyright` distribution on
|
|
95
|
-
PyPI
|
|
96
|
-
|
|
97
|
-
the same build, so only this direct invocation needs the
|
|
95
|
+
PyPI is released separately from the npm engine it launches, and the two have drifted apart before: PyPI
|
|
96
|
+
stayed at 1.1.411 from June to September while npm shipped two builds. The baseline is recorded against
|
|
97
|
+
one engine, and `tests/typing_harness.py` pins the same build, so only this direct invocation needs the
|
|
98
|
+
variable.
|
|
98
99
|
|
|
99
100
|
`--pythonversion 3.14` is not decoration. Pyright reports against the interpreter it finds unless
|
|
100
101
|
told otherwise, and the count moves with it: 174 diagnostics for this package on 3.10 against 169 on
|
|
@@ -123,9 +124,11 @@ Write tests for every new feature or bug fix. Use `assertpy2` assertions in test
|
|
|
123
124
|
|
|
124
125
|
## Documentation examples
|
|
125
126
|
|
|
126
|
-
Guide code blocks are
|
|
127
|
+
Guide code blocks are type-checked in CI, and every page that can run in the suite is executed, with what
|
|
128
|
+
each block prints compared with the comment beside it, so an example you add has to run. A block that
|
|
127
129
|
cannot (pseudo-context, a deliberate failure, a rejected counter-example) is marked with an HTML comment
|
|
128
|
-
above the fence
|
|
130
|
+
above the fence. `tests/test_docs_examples.py` lists the markers and what each one exempts, and it and
|
|
131
|
+
`tests/test_docs_typing.py` each name the pages they leave out, with the reason.
|
|
129
132
|
|
|
130
133
|
Setup a page assumes (a domain class, a repository, an HTTP response) goes in
|
|
131
134
|
`tests/docs_fixtures.py`, not into an extra block on the page.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: assertpy2
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.26.0
|
|
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
|
|
@@ -30,13 +30,13 @@ Requires-Dist: typing-extensions>=4.0; python_version < '3.11'
|
|
|
30
30
|
Provides-Extra: allure
|
|
31
31
|
Requires-Dist: allure-pytest>=2.13; extra == 'allure'
|
|
32
32
|
Provides-Extra: behave
|
|
33
|
-
Requires-Dist: behave>=1.
|
|
33
|
+
Requires-Dist: behave>=1.3.0; extra == 'behave'
|
|
34
34
|
Provides-Extra: data
|
|
35
35
|
Requires-Dist: numpy>=1.26; extra == 'data'
|
|
36
36
|
Requires-Dist: pandas>=2.0; extra == 'data'
|
|
37
37
|
Requires-Dist: polars>=1.0; extra == 'data'
|
|
38
38
|
Provides-Extra: inline
|
|
39
|
-
Requires-Dist: asttokens>=2.0; extra == 'inline'
|
|
39
|
+
Requires-Dist: asttokens>=2.0.4; extra == 'inline'
|
|
40
40
|
Requires-Dist: executing>=2.2; extra == 'inline'
|
|
41
41
|
Provides-Extra: json
|
|
42
42
|
Requires-Dist: jsonpath-ng>=1.8; extra == 'json'
|
|
@@ -130,7 +130,7 @@ Allure, Behave, JSON Path and Schema, pandas, polars, numpy, and OpenAPI respons
|
|
|
130
130
|
|
|
131
131
|
<h2 align="center"><a href="https://solganis.github.io/assertpy2/getting-started/comparison/">Why fluent assertions?</a></h2>
|
|
132
132
|
|
|
133
|
-
`assert` states a condition well
|
|
133
|
+
`assert` states a condition well. pytest reports it well.
|
|
134
134
|
|
|
135
135
|
What it cannot say is *where* two structures differ. It prints both and leaves the reading to you:
|
|
136
136
|
|
|
@@ -154,12 +154,12 @@ assert_that(response).is_equal_to(expected)
|
|
|
154
154
|
<img src="https://raw.githubusercontent.com/Solganis/assertpy2/main/docs/assets/diff-equal.png" width="300" alt="Structured diff in the terminal: user.role shown with its path, removal in red and addition in green">
|
|
155
155
|
</p>
|
|
156
156
|
|
|
157
|
-
It recurses through nested containers
|
|
157
|
+
It recurses through nested containers. Matcher predicates get the same treatment.
|
|
158
158
|
|
|
159
159
|
For dynamic fields like IDs, assert a subset with
|
|
160
160
|
[`matches_structure()`](https://solganis.github.io/assertpy2/guides/matchers/#structural-matching).
|
|
161
161
|
|
|
162
|
-
The chain is the other half
|
|
162
|
+
The chain is the other half. Your IDE offers only the
|
|
163
163
|
[methods that fit the value](https://solganis.github.io/assertpy2/concepts/type-safety/).
|
|
164
164
|
|
|
165
165
|
<!-- docs-guard: skip -->
|
|
@@ -169,7 +169,7 @@ assert_that(items).is_instance_of(list).is_length(3).contains("admin")
|
|
|
169
169
|
|
|
170
170
|
Matchers are ordinary values that answer `==`, the way `unittest.mock.ANY` does.
|
|
171
171
|
|
|
172
|
-
Nothing is patched, so a matcher can sit inside the expected structure
|
|
172
|
+
Nothing is patched, so a matcher can sit inside the expected structure at any depth:
|
|
173
173
|
|
|
174
174
|
```python
|
|
175
175
|
response = {"id": 7, "user": {"name": "Alice", "age": 30}, "tags": ["a", "b"]}
|
|
@@ -210,7 +210,7 @@ Your IDE shows only methods relevant to the value you're testing, not the whole
|
|
|
210
210
|
- `assert_that(b"\x89PNG").` → bytes methods: `starts_with_bytes`, `is_valid_utf8`, `decoded_as`, ...
|
|
211
211
|
|
|
212
212
|
15 type-specific Protocols instead of one `Any`.<br>
|
|
213
|
-
Works in PyCharm, VS Code, and any
|
|
213
|
+
Works in PyCharm, VS Code, and any editor that runs a type checker.
|
|
214
214
|
|
|
215
215
|
<h2 align="center"><a href="https://solganis.github.io/assertpy2/concepts/type-safety/#typed-narrowing-with-value">Typed narrowing</a></h2>
|
|
216
216
|
|
|
@@ -235,7 +235,7 @@ data = assert_conforms(response.json(), OrderModel).value # data: OrderModel
|
|
|
235
235
|
|
|
236
236
|
<h2 align="center"><a href="https://solganis.github.io/assertpy2/guides/errors/#asking-instead-of-asserting">A failure you can read from code</a></h2>
|
|
237
237
|
|
|
238
|
-
An exception is the right default
|
|
238
|
+
An exception is the right default. Reading the result means catching it.
|
|
239
239
|
|
|
240
240
|
`check()` runs the next assertion for its verdict instead:
|
|
241
241
|
|
|
@@ -250,11 +250,11 @@ if not outcome and outcome.diff:
|
|
|
250
250
|
```
|
|
251
251
|
|
|
252
252
|
It is truthy when the assertion held. When it did not, it carries `.message`, `.actual`, `.expected`
|
|
253
|
-
and a walkable `.diff
|
|
253
|
+
and a walkable `.diff`. So does `AssertionFailure`.
|
|
254
254
|
|
|
255
255
|
So a reporter reads structure instead of parsing a string. That is how the
|
|
256
|
-
[Allure integration](https://solganis.github.io/assertpy2/extending/integrations/#allure) works
|
|
257
|
-
|
|
256
|
+
[Allure integration](https://solganis.github.io/assertpy2/extending/integrations/#allure) works.
|
|
257
|
+
Anything else you build can too.
|
|
258
258
|
|
|
259
259
|
<h2 align="center">Features</h2>
|
|
260
260
|
|
|
@@ -262,8 +262,6 @@ is open to anything else you build.
|
|
|
262
262
|
|
|
263
263
|
- [**Structural matching**](https://solganis.github.io/assertpy2/guides/matchers/#structural-matching): `matches_structure()` for declarative dict/API-response validation.
|
|
264
264
|
- [**Recursive field assertions**](https://solganis.github.io/assertpy2/guides/assertions/#recursive-field-assertions): `all_fields_satisfy()` / `has_no_none_fields()` apply a predicate to every leaf of an object graph.
|
|
265
|
-
- [**Vacuous-assertion guard**](https://solganis.github.io/assertpy2/guides/assertions/#assertions-that-checked-nothing): `--assertpy2-vacuous` warns when a universal assertion passes over an empty collection, having checked nothing.
|
|
266
|
-
- [**Dangling-assertion detector**](https://solganis.github.io/assertpy2/guides/assertions/#assertions-that-never-ran): `--assertpy2-dangling` warns when a chain builds an assertion and never runs it. `assert assert_that(x).is_positive` passes on any value, and neither ruff nor coverage sees it.
|
|
267
265
|
- [**Universal negation**](https://solganis.github.io/assertpy2/guides/fluent/#universal-negation): `.not_` inverts any assertion, no dedicated `is_not_*` methods.
|
|
268
266
|
- [**Collection pipeline**](https://solganis.github.io/assertpy2/guides/fluent/#collection-pipeline): `filtered_on()`, `mapped()`, `flat_mapped()`, `first()`, `last()`, `element()`, `single()`.
|
|
269
267
|
- [**Positional & pairwise checks**](https://solganis.github.io/assertpy2/guides/assertions/#lists): `satisfies_exactly()`, `zip_satisfies()`, `contains_only_once()`, `has_same_size_as()`, plus `*_in_any_order` variants.
|
|
@@ -281,6 +279,7 @@ is open to anything else you build.
|
|
|
281
279
|
- [**Dict comparison**](https://solganis.github.io/assertpy2/guides/assertions/#selective-comparison-ignore--include): `is_equal_to(ignore=..., include=...)` for selective key/field matching by name, regex, or type.
|
|
282
280
|
- [**Recursive comparison**](https://solganis.github.io/assertpy2/guides/assertions/#recursive-comparison-tolerance--custom-comparators): `is_equal_to()` with `tolerance`, `comparators`, or `ignore_null` for nested structures.
|
|
283
281
|
- [**Extracting**](https://solganis.github.io/assertpy2/guides/assertions/#extracting-attributes-from-objects): flatten collections on attributes with `filter` and `sort` support.
|
|
282
|
+
- [**Regex group extraction**](https://solganis.github.io/assertpy2/guides/data/#regex-group-extraction): `extracting_group()` and `matches_with_groups()` for regex captures.
|
|
284
283
|
|
|
285
284
|
**Testing**
|
|
286
285
|
|
|
@@ -288,17 +287,28 @@ is open to anything else you build.
|
|
|
288
287
|
- [**Polling assertions**](https://solganis.github.io/assertpy2/guides/testing/#async-assertions): `eventually()` (async) / `eventually_sync()` (blocking) retry for eventual consistency, with a convergence trace on timeout.
|
|
289
288
|
- [**Expected exceptions**](https://solganis.github.io/assertpy2/guides/errors/#expected-exceptions): `raises().when_called_with()`, walk the cause chain (`caused_by()`, `has_root_cause()`), search an `ExceptionGroup` (`contains_error()`, `errors()`, `error_of()`), or pivot to the object (`raised()`).
|
|
290
289
|
- [**HTTP responses**](https://solganis.github.io/assertpy2/recipes/#test-an-http-api-response): assert on the response itself and every failure names the request it came from, with `decoded_as_json()` to step into the body. No client library is a dependency.
|
|
290
|
+
- [**Snapshot testing**](https://solganis.github.io/assertpy2/guides/testing/#snapshot-testing): an external JSON file, an [inline](https://solganis.github.io/assertpy2/guides/testing/#inline-snapshots) value recorded into the test source, or a [value-tolerant contract](https://solganis.github.io/assertpy2/guides/testing/#contract-snapshots), all updated with `--assertpy2-snapshot-update`.
|
|
291
|
+
- [**OpenAPI response contracts**](https://solganis.github.io/assertpy2/reference/json/#assertpy2.json_mixin.JsonMixin.conforms_to_openapi): `conforms_to_openapi()` checks a JSON body against an operation's response schema, reporting every violation with its JSON path.
|
|
292
|
+
|
|
293
|
+
**Failure reporting**
|
|
294
|
+
|
|
291
295
|
- [**Structured errors**](https://solganis.github.io/assertpy2/guides/errors/#structured-errors): `AssertionFailure` carries `.actual`, `.expected` and `.diff`, and the diff renders into the message, so it shows off pytest too.
|
|
292
296
|
- [**Assertions as values**](https://solganis.github.io/assertpy2/guides/errors/#asking-instead-of-asserting): `check()` runs the next assertion for its verdict instead of raising, handing back an `AssertionOutcome`.
|
|
293
297
|
- [**Rich pytest diffs**](https://solganis.github.io/assertpy2/guides/errors/#rich-pytest-diffs): recursive diffs across containers, dataclasses, attrs and Pydantic models, with intra-line carets for strings.
|
|
294
|
-
|
|
295
|
-
|
|
298
|
+
|
|
299
|
+
**Plugin for pytest**
|
|
300
|
+
|
|
301
|
+
Set from the command line or from `[tool.pytest.ini_options]`, not from a call.
|
|
302
|
+
|
|
303
|
+
- [**Failure clustering**](https://solganis.github.io/assertpy2/guides/errors/#what-the-failures-had-in-common): forty failing tests are usually not forty problems. Where three or more differ at the same place, the run ends with a line saying where. On by default, `assertpy2_failure_clusters = "off"` turns it off.
|
|
304
|
+
- [**Diagnostic profiles**](https://solganis.github.io/assertpy2/guides/errors/#configuration): `assertpy2_profile` turns the guards below on in one line. `compatible` (default) leaves them off, `safe` warns, `strict` fails the tests they find. A setting you name yourself still wins.
|
|
305
|
+
- [**Vacuous-assertion guard**](https://solganis.github.io/assertpy2/guides/assertions/#assertions-that-checked-nothing): `--assertpy2-vacuous` warns when a universal assertion passes over an empty collection, having checked nothing.
|
|
306
|
+
- [**Dangling-assertion detector**](https://solganis.github.io/assertpy2/guides/assertions/#assertions-that-never-ran): `--assertpy2-dangling` warns when a chain builds an assertion and never runs it. `assert assert_that(x).is_positive` passes on any value, and neither ruff nor coverage sees it.
|
|
296
307
|
|
|
297
308
|
**Extensibility**
|
|
298
309
|
|
|
299
310
|
- [**Custom matchers**](https://solganis.github.io/assertpy2/guides/matchers/#custom-matchers): `register_matcher()` composes existing ones, `BaseMatcher` carries its own predicate. Both compose with `&`, `|`, `~`.
|
|
300
311
|
- [**Custom assertions**](https://solganis.github.io/assertpy2/extending/custom-assertions/): `add_extension()` adds a method to the builder.
|
|
301
|
-
- [**Regex group extraction**](https://solganis.github.io/assertpy2/guides/data/#regex-group-extraction): `extracting_group()` and `matches_with_groups()` for regex captures.
|
|
302
312
|
|
|
303
313
|
<h2 align="center"><a href="https://solganis.github.io/assertpy2/extending/integrations/">Integrations</a></h2>
|
|
304
314
|
|
|
@@ -78,7 +78,7 @@ Allure, Behave, JSON Path and Schema, pandas, polars, numpy, and OpenAPI respons
|
|
|
78
78
|
|
|
79
79
|
<h2 align="center"><a href="https://solganis.github.io/assertpy2/getting-started/comparison/">Why fluent assertions?</a></h2>
|
|
80
80
|
|
|
81
|
-
`assert` states a condition well
|
|
81
|
+
`assert` states a condition well. pytest reports it well.
|
|
82
82
|
|
|
83
83
|
What it cannot say is *where* two structures differ. It prints both and leaves the reading to you:
|
|
84
84
|
|
|
@@ -102,12 +102,12 @@ assert_that(response).is_equal_to(expected)
|
|
|
102
102
|
<img src="https://raw.githubusercontent.com/Solganis/assertpy2/main/docs/assets/diff-equal.png" width="300" alt="Structured diff in the terminal: user.role shown with its path, removal in red and addition in green">
|
|
103
103
|
</p>
|
|
104
104
|
|
|
105
|
-
It recurses through nested containers
|
|
105
|
+
It recurses through nested containers. Matcher predicates get the same treatment.
|
|
106
106
|
|
|
107
107
|
For dynamic fields like IDs, assert a subset with
|
|
108
108
|
[`matches_structure()`](https://solganis.github.io/assertpy2/guides/matchers/#structural-matching).
|
|
109
109
|
|
|
110
|
-
The chain is the other half
|
|
110
|
+
The chain is the other half. Your IDE offers only the
|
|
111
111
|
[methods that fit the value](https://solganis.github.io/assertpy2/concepts/type-safety/).
|
|
112
112
|
|
|
113
113
|
<!-- docs-guard: skip -->
|
|
@@ -117,7 +117,7 @@ assert_that(items).is_instance_of(list).is_length(3).contains("admin")
|
|
|
117
117
|
|
|
118
118
|
Matchers are ordinary values that answer `==`, the way `unittest.mock.ANY` does.
|
|
119
119
|
|
|
120
|
-
Nothing is patched, so a matcher can sit inside the expected structure
|
|
120
|
+
Nothing is patched, so a matcher can sit inside the expected structure at any depth:
|
|
121
121
|
|
|
122
122
|
```python
|
|
123
123
|
response = {"id": 7, "user": {"name": "Alice", "age": 30}, "tags": ["a", "b"]}
|
|
@@ -158,7 +158,7 @@ Your IDE shows only methods relevant to the value you're testing, not the whole
|
|
|
158
158
|
- `assert_that(b"\x89PNG").` → bytes methods: `starts_with_bytes`, `is_valid_utf8`, `decoded_as`, ...
|
|
159
159
|
|
|
160
160
|
15 type-specific Protocols instead of one `Any`.<br>
|
|
161
|
-
Works in PyCharm, VS Code, and any
|
|
161
|
+
Works in PyCharm, VS Code, and any editor that runs a type checker.
|
|
162
162
|
|
|
163
163
|
<h2 align="center"><a href="https://solganis.github.io/assertpy2/concepts/type-safety/#typed-narrowing-with-value">Typed narrowing</a></h2>
|
|
164
164
|
|
|
@@ -183,7 +183,7 @@ data = assert_conforms(response.json(), OrderModel).value # data: OrderModel
|
|
|
183
183
|
|
|
184
184
|
<h2 align="center"><a href="https://solganis.github.io/assertpy2/guides/errors/#asking-instead-of-asserting">A failure you can read from code</a></h2>
|
|
185
185
|
|
|
186
|
-
An exception is the right default
|
|
186
|
+
An exception is the right default. Reading the result means catching it.
|
|
187
187
|
|
|
188
188
|
`check()` runs the next assertion for its verdict instead:
|
|
189
189
|
|
|
@@ -198,11 +198,11 @@ if not outcome and outcome.diff:
|
|
|
198
198
|
```
|
|
199
199
|
|
|
200
200
|
It is truthy when the assertion held. When it did not, it carries `.message`, `.actual`, `.expected`
|
|
201
|
-
and a walkable `.diff
|
|
201
|
+
and a walkable `.diff`. So does `AssertionFailure`.
|
|
202
202
|
|
|
203
203
|
So a reporter reads structure instead of parsing a string. That is how the
|
|
204
|
-
[Allure integration](https://solganis.github.io/assertpy2/extending/integrations/#allure) works
|
|
205
|
-
|
|
204
|
+
[Allure integration](https://solganis.github.io/assertpy2/extending/integrations/#allure) works.
|
|
205
|
+
Anything else you build can too.
|
|
206
206
|
|
|
207
207
|
<h2 align="center">Features</h2>
|
|
208
208
|
|
|
@@ -210,8 +210,6 @@ is open to anything else you build.
|
|
|
210
210
|
|
|
211
211
|
- [**Structural matching**](https://solganis.github.io/assertpy2/guides/matchers/#structural-matching): `matches_structure()` for declarative dict/API-response validation.
|
|
212
212
|
- [**Recursive field assertions**](https://solganis.github.io/assertpy2/guides/assertions/#recursive-field-assertions): `all_fields_satisfy()` / `has_no_none_fields()` apply a predicate to every leaf of an object graph.
|
|
213
|
-
- [**Vacuous-assertion guard**](https://solganis.github.io/assertpy2/guides/assertions/#assertions-that-checked-nothing): `--assertpy2-vacuous` warns when a universal assertion passes over an empty collection, having checked nothing.
|
|
214
|
-
- [**Dangling-assertion detector**](https://solganis.github.io/assertpy2/guides/assertions/#assertions-that-never-ran): `--assertpy2-dangling` warns when a chain builds an assertion and never runs it. `assert assert_that(x).is_positive` passes on any value, and neither ruff nor coverage sees it.
|
|
215
213
|
- [**Universal negation**](https://solganis.github.io/assertpy2/guides/fluent/#universal-negation): `.not_` inverts any assertion, no dedicated `is_not_*` methods.
|
|
216
214
|
- [**Collection pipeline**](https://solganis.github.io/assertpy2/guides/fluent/#collection-pipeline): `filtered_on()`, `mapped()`, `flat_mapped()`, `first()`, `last()`, `element()`, `single()`.
|
|
217
215
|
- [**Positional & pairwise checks**](https://solganis.github.io/assertpy2/guides/assertions/#lists): `satisfies_exactly()`, `zip_satisfies()`, `contains_only_once()`, `has_same_size_as()`, plus `*_in_any_order` variants.
|
|
@@ -229,6 +227,7 @@ is open to anything else you build.
|
|
|
229
227
|
- [**Dict comparison**](https://solganis.github.io/assertpy2/guides/assertions/#selective-comparison-ignore--include): `is_equal_to(ignore=..., include=...)` for selective key/field matching by name, regex, or type.
|
|
230
228
|
- [**Recursive comparison**](https://solganis.github.io/assertpy2/guides/assertions/#recursive-comparison-tolerance--custom-comparators): `is_equal_to()` with `tolerance`, `comparators`, or `ignore_null` for nested structures.
|
|
231
229
|
- [**Extracting**](https://solganis.github.io/assertpy2/guides/assertions/#extracting-attributes-from-objects): flatten collections on attributes with `filter` and `sort` support.
|
|
230
|
+
- [**Regex group extraction**](https://solganis.github.io/assertpy2/guides/data/#regex-group-extraction): `extracting_group()` and `matches_with_groups()` for regex captures.
|
|
232
231
|
|
|
233
232
|
**Testing**
|
|
234
233
|
|
|
@@ -236,17 +235,28 @@ is open to anything else you build.
|
|
|
236
235
|
- [**Polling assertions**](https://solganis.github.io/assertpy2/guides/testing/#async-assertions): `eventually()` (async) / `eventually_sync()` (blocking) retry for eventual consistency, with a convergence trace on timeout.
|
|
237
236
|
- [**Expected exceptions**](https://solganis.github.io/assertpy2/guides/errors/#expected-exceptions): `raises().when_called_with()`, walk the cause chain (`caused_by()`, `has_root_cause()`), search an `ExceptionGroup` (`contains_error()`, `errors()`, `error_of()`), or pivot to the object (`raised()`).
|
|
238
237
|
- [**HTTP responses**](https://solganis.github.io/assertpy2/recipes/#test-an-http-api-response): assert on the response itself and every failure names the request it came from, with `decoded_as_json()` to step into the body. No client library is a dependency.
|
|
238
|
+
- [**Snapshot testing**](https://solganis.github.io/assertpy2/guides/testing/#snapshot-testing): an external JSON file, an [inline](https://solganis.github.io/assertpy2/guides/testing/#inline-snapshots) value recorded into the test source, or a [value-tolerant contract](https://solganis.github.io/assertpy2/guides/testing/#contract-snapshots), all updated with `--assertpy2-snapshot-update`.
|
|
239
|
+
- [**OpenAPI response contracts**](https://solganis.github.io/assertpy2/reference/json/#assertpy2.json_mixin.JsonMixin.conforms_to_openapi): `conforms_to_openapi()` checks a JSON body against an operation's response schema, reporting every violation with its JSON path.
|
|
240
|
+
|
|
241
|
+
**Failure reporting**
|
|
242
|
+
|
|
239
243
|
- [**Structured errors**](https://solganis.github.io/assertpy2/guides/errors/#structured-errors): `AssertionFailure` carries `.actual`, `.expected` and `.diff`, and the diff renders into the message, so it shows off pytest too.
|
|
240
244
|
- [**Assertions as values**](https://solganis.github.io/assertpy2/guides/errors/#asking-instead-of-asserting): `check()` runs the next assertion for its verdict instead of raising, handing back an `AssertionOutcome`.
|
|
241
245
|
- [**Rich pytest diffs**](https://solganis.github.io/assertpy2/guides/errors/#rich-pytest-diffs): recursive diffs across containers, dataclasses, attrs and Pydantic models, with intra-line carets for strings.
|
|
242
|
-
|
|
243
|
-
|
|
246
|
+
|
|
247
|
+
**Plugin for pytest**
|
|
248
|
+
|
|
249
|
+
Set from the command line or from `[tool.pytest.ini_options]`, not from a call.
|
|
250
|
+
|
|
251
|
+
- [**Failure clustering**](https://solganis.github.io/assertpy2/guides/errors/#what-the-failures-had-in-common): forty failing tests are usually not forty problems. Where three or more differ at the same place, the run ends with a line saying where. On by default, `assertpy2_failure_clusters = "off"` turns it off.
|
|
252
|
+
- [**Diagnostic profiles**](https://solganis.github.io/assertpy2/guides/errors/#configuration): `assertpy2_profile` turns the guards below on in one line. `compatible` (default) leaves them off, `safe` warns, `strict` fails the tests they find. A setting you name yourself still wins.
|
|
253
|
+
- [**Vacuous-assertion guard**](https://solganis.github.io/assertpy2/guides/assertions/#assertions-that-checked-nothing): `--assertpy2-vacuous` warns when a universal assertion passes over an empty collection, having checked nothing.
|
|
254
|
+
- [**Dangling-assertion detector**](https://solganis.github.io/assertpy2/guides/assertions/#assertions-that-never-ran): `--assertpy2-dangling` warns when a chain builds an assertion and never runs it. `assert assert_that(x).is_positive` passes on any value, and neither ruff nor coverage sees it.
|
|
244
255
|
|
|
245
256
|
**Extensibility**
|
|
246
257
|
|
|
247
258
|
- [**Custom matchers**](https://solganis.github.io/assertpy2/guides/matchers/#custom-matchers): `register_matcher()` composes existing ones, `BaseMatcher` carries its own predicate. Both compose with `&`, `|`, `~`.
|
|
248
259
|
- [**Custom assertions**](https://solganis.github.io/assertpy2/extending/custom-assertions/): `add_extension()` adds a method to the builder.
|
|
249
|
-
- [**Regex group extraction**](https://solganis.github.io/assertpy2/guides/data/#regex-group-extraction): `extracting_group()` and `matches_with_groups()` for regex captures.
|
|
250
260
|
|
|
251
261
|
<h2 align="center"><a href="https://solganis.github.io/assertpy2/extending/integrations/">Integrations</a></h2>
|
|
252
262
|
|
|
@@ -35,7 +35,7 @@ from .matchers import (
|
|
|
35
35
|
register_matcher,
|
|
36
36
|
unregister_matcher,
|
|
37
37
|
)
|
|
38
|
-
from .outcome import AssertionOutcome
|
|
38
|
+
from .outcome import AssertionOutcome, Requirement
|
|
39
39
|
from .snapshot import (
|
|
40
40
|
SnapshotCreatedWarning,
|
|
41
41
|
SnapshotKeyReusedWarning,
|
|
@@ -57,6 +57,7 @@ __all__ = [
|
|
|
57
57
|
"NegatedBuilder",
|
|
58
58
|
"PollSample",
|
|
59
59
|
"PollTrace",
|
|
60
|
+
"Requirement",
|
|
60
61
|
"SnapshotCreatedWarning",
|
|
61
62
|
"SnapshotKeyReusedWarning",
|
|
62
63
|
"SnapshotUpdatedWarning",
|
|
@@ -26,7 +26,15 @@ if TYPE_CHECKING:
|
|
|
26
26
|
from .._matcher_impls import ClassInfo
|
|
27
27
|
from ..matchers import Matcher
|
|
28
28
|
from ..outcome import AssertionOutcome
|
|
29
|
-
from ._capable_typing import
|
|
29
|
+
from ._capable_typing import (
|
|
30
|
+
_Callable,
|
|
31
|
+
_Indexed,
|
|
32
|
+
_Keyed,
|
|
33
|
+
_KeyedWithItems,
|
|
34
|
+
_KeyedWithValues,
|
|
35
|
+
_Orderable,
|
|
36
|
+
_PathLike,
|
|
37
|
+
)
|
|
30
38
|
from ._introspection import MappingLike
|
|
31
39
|
from ._typing import (
|
|
32
40
|
_U,
|
|
@@ -551,6 +559,53 @@ if TYPE_CHECKING:
|
|
|
551
559
|
|
|
552
560
|
def matches_with_groups(self: _CheckAnyValue[str], pattern: str) -> AssertionOutcome: ...
|
|
553
561
|
|
|
562
|
+
@overload
|
|
563
|
+
def exists(self: _CheckAnyValue[str] | _CheckAnyValue[pathlib.Path]) -> AssertionOutcome: ...
|
|
564
|
+
@overload
|
|
565
|
+
def exists(self: _CheckAnyValue[_PathLike]) -> AssertionOutcome: ...
|
|
566
|
+
|
|
567
|
+
@overload
|
|
568
|
+
def does_not_exist(self: _CheckAnyValue[str] | _CheckAnyValue[pathlib.Path]) -> AssertionOutcome: ...
|
|
569
|
+
@overload
|
|
570
|
+
def does_not_exist(self: _CheckAnyValue[_PathLike]) -> AssertionOutcome: ...
|
|
571
|
+
|
|
572
|
+
@overload
|
|
573
|
+
def is_file(self: _CheckAnyValue[str] | _CheckAnyValue[pathlib.Path]) -> AssertionOutcome: ...
|
|
574
|
+
@overload
|
|
575
|
+
def is_file(self: _CheckAnyValue[_PathLike]) -> AssertionOutcome: ...
|
|
576
|
+
|
|
577
|
+
@overload
|
|
578
|
+
def is_directory(self: _CheckAnyValue[str] | _CheckAnyValue[pathlib.Path]) -> AssertionOutcome: ...
|
|
579
|
+
@overload
|
|
580
|
+
def is_directory(self: _CheckAnyValue[_PathLike]) -> AssertionOutcome: ...
|
|
581
|
+
|
|
582
|
+
@overload
|
|
583
|
+
def is_named(self: _CheckAnyValue[str] | _CheckAnyValue[pathlib.Path], filename: str) -> AssertionOutcome: ...
|
|
584
|
+
@overload
|
|
585
|
+
def is_named(self: _CheckAnyValue[_PathLike], filename: str) -> AssertionOutcome: ...
|
|
586
|
+
|
|
587
|
+
@overload
|
|
588
|
+
def is_child_of(
|
|
589
|
+
self: _CheckAnyValue[str] | _CheckAnyValue[pathlib.Path], parent: object
|
|
590
|
+
) -> AssertionOutcome: ...
|
|
591
|
+
@overload
|
|
592
|
+
def is_child_of(self: _CheckAnyValue[_PathLike], parent: object) -> AssertionOutcome: ...
|
|
593
|
+
|
|
594
|
+
@overload
|
|
595
|
+
def is_readable(self: _CheckAnyValue[str] | _CheckAnyValue[pathlib.Path]) -> AssertionOutcome: ...
|
|
596
|
+
@overload
|
|
597
|
+
def is_readable(self: _CheckAnyValue[_PathLike]) -> AssertionOutcome: ...
|
|
598
|
+
|
|
599
|
+
@overload
|
|
600
|
+
def is_writable(self: _CheckAnyValue[str] | _CheckAnyValue[pathlib.Path]) -> AssertionOutcome: ...
|
|
601
|
+
@overload
|
|
602
|
+
def is_writable(self: _CheckAnyValue[_PathLike]) -> AssertionOutcome: ...
|
|
603
|
+
|
|
604
|
+
@overload
|
|
605
|
+
def is_executable(self: _CheckAnyValue[str] | _CheckAnyValue[pathlib.Path]) -> AssertionOutcome: ...
|
|
606
|
+
@overload
|
|
607
|
+
def is_executable(self: _CheckAnyValue[_PathLike]) -> AssertionOutcome: ...
|
|
608
|
+
|
|
554
609
|
@overload
|
|
555
610
|
def is_subset_of(
|
|
556
611
|
self: _CheckAnyValue[str]
|
|
@@ -574,10 +629,11 @@ if TYPE_CHECKING:
|
|
|
574
629
|
self: _CheckAnyValue[_CapableT], *supersets: object, allow_empty: bool = ...
|
|
575
630
|
) -> AssertionOutcome: ...
|
|
576
631
|
|
|
632
|
+
@overload
|
|
633
|
+
def contains_duplicates(self: _CheckAnyValue[str]) -> AssertionOutcome: ...
|
|
577
634
|
@overload
|
|
578
635
|
def contains_duplicates(
|
|
579
|
-
self: _CheckAnyValue[
|
|
580
|
-
| _CheckAnyValue[list[_E]]
|
|
636
|
+
self: _CheckAnyValue[list[_E]]
|
|
581
637
|
| _CheckAnyValue[tuple[_E, ...]]
|
|
582
638
|
| _CheckAnyValue[set[_E]]
|
|
583
639
|
| _CheckAnyValue[frozenset[_E]],
|
|
@@ -585,10 +641,11 @@ if TYPE_CHECKING:
|
|
|
585
641
|
@overload
|
|
586
642
|
def contains_duplicates(self: _CheckAnyValue[_CapableT]) -> AssertionOutcome: ...
|
|
587
643
|
|
|
644
|
+
@overload
|
|
645
|
+
def does_not_contain_duplicates(self: _CheckAnyValue[str]) -> AssertionOutcome: ...
|
|
588
646
|
@overload
|
|
589
647
|
def does_not_contain_duplicates(
|
|
590
|
-
self: _CheckAnyValue[
|
|
591
|
-
| _CheckAnyValue[list[_E]]
|
|
648
|
+
self: _CheckAnyValue[list[_E]]
|
|
592
649
|
| _CheckAnyValue[tuple[_E, ...]]
|
|
593
650
|
| _CheckAnyValue[set[_E]]
|
|
594
651
|
| _CheckAnyValue[frozenset[_E]],
|
|
@@ -596,10 +653,11 @@ if TYPE_CHECKING:
|
|
|
596
653
|
@overload
|
|
597
654
|
def does_not_contain_duplicates(self: _CheckAnyValue[_CapableT]) -> AssertionOutcome: ...
|
|
598
655
|
|
|
656
|
+
@overload
|
|
657
|
+
def contains_only_once(self: _CheckAnyValue[str], *items: object) -> AssertionOutcome: ...
|
|
599
658
|
@overload
|
|
600
659
|
def contains_only_once(
|
|
601
|
-
self: _CheckAnyValue[
|
|
602
|
-
| _CheckAnyValue[list[_E]]
|
|
660
|
+
self: _CheckAnyValue[list[_E]]
|
|
603
661
|
| _CheckAnyValue[tuple[_E, ...]]
|
|
604
662
|
| _CheckAnyValue[set[_E]]
|
|
605
663
|
| _CheckAnyValue[frozenset[_E]],
|
|
@@ -608,17 +666,20 @@ if TYPE_CHECKING:
|
|
|
608
666
|
@overload
|
|
609
667
|
def contains_only_once(self: _CheckAnyValue[_CapableT], *items: object) -> AssertionOutcome: ...
|
|
610
668
|
|
|
669
|
+
@overload
|
|
670
|
+
def contains_in_order(self: _CheckAnyValue[str], *items: str | Matcher[str]) -> AssertionOutcome: ...
|
|
611
671
|
@overload
|
|
612
672
|
def contains_in_order(
|
|
613
|
-
self: _CheckAnyValue[
|
|
614
|
-
| _CheckAnyValue[list[_E]]
|
|
673
|
+
self: _CheckAnyValue[list[_E]]
|
|
615
674
|
| _CheckAnyValue[tuple[_E, ...]]
|
|
616
675
|
| _CheckAnyValue[set[_E]]
|
|
617
676
|
| _CheckAnyValue[frozenset[_E]],
|
|
618
677
|
*items: _E | Matcher[_E],
|
|
619
678
|
) -> AssertionOutcome: ...
|
|
620
679
|
@overload
|
|
621
|
-
def contains_in_order(
|
|
680
|
+
def contains_in_order(
|
|
681
|
+
self: _CheckAnyValue[Iterable[_E] | _Indexed[_E]], *items: _E | Matcher[_E]
|
|
682
|
+
) -> AssertionOutcome: ...
|
|
622
683
|
|
|
623
684
|
@overload
|
|
624
685
|
def has_same_size_as(
|
|
@@ -721,53 +782,6 @@ if TYPE_CHECKING:
|
|
|
721
782
|
@overload
|
|
722
783
|
def is_not_empty(self: _CheckAnyValue[_CapableT]) -> AssertionOutcome: ...
|
|
723
784
|
|
|
724
|
-
@overload
|
|
725
|
-
def exists(self: _CheckAnyValue[str] | _CheckAnyValue[pathlib.Path]) -> AssertionOutcome: ...
|
|
726
|
-
@overload
|
|
727
|
-
def exists(self: _CheckAnyValue[_PathLike]) -> AssertionOutcome: ...
|
|
728
|
-
|
|
729
|
-
@overload
|
|
730
|
-
def does_not_exist(self: _CheckAnyValue[str] | _CheckAnyValue[pathlib.Path]) -> AssertionOutcome: ...
|
|
731
|
-
@overload
|
|
732
|
-
def does_not_exist(self: _CheckAnyValue[_PathLike]) -> AssertionOutcome: ...
|
|
733
|
-
|
|
734
|
-
@overload
|
|
735
|
-
def is_file(self: _CheckAnyValue[str] | _CheckAnyValue[pathlib.Path]) -> AssertionOutcome: ...
|
|
736
|
-
@overload
|
|
737
|
-
def is_file(self: _CheckAnyValue[_PathLike]) -> AssertionOutcome: ...
|
|
738
|
-
|
|
739
|
-
@overload
|
|
740
|
-
def is_directory(self: _CheckAnyValue[str] | _CheckAnyValue[pathlib.Path]) -> AssertionOutcome: ...
|
|
741
|
-
@overload
|
|
742
|
-
def is_directory(self: _CheckAnyValue[_PathLike]) -> AssertionOutcome: ...
|
|
743
|
-
|
|
744
|
-
@overload
|
|
745
|
-
def is_named(self: _CheckAnyValue[str] | _CheckAnyValue[pathlib.Path], filename: str) -> AssertionOutcome: ...
|
|
746
|
-
@overload
|
|
747
|
-
def is_named(self: _CheckAnyValue[_PathLike], filename: str) -> AssertionOutcome: ...
|
|
748
|
-
|
|
749
|
-
@overload
|
|
750
|
-
def is_child_of(
|
|
751
|
-
self: _CheckAnyValue[str] | _CheckAnyValue[pathlib.Path], parent: object
|
|
752
|
-
) -> AssertionOutcome: ...
|
|
753
|
-
@overload
|
|
754
|
-
def is_child_of(self: _CheckAnyValue[_PathLike], parent: object) -> AssertionOutcome: ...
|
|
755
|
-
|
|
756
|
-
@overload
|
|
757
|
-
def is_readable(self: _CheckAnyValue[str] | _CheckAnyValue[pathlib.Path]) -> AssertionOutcome: ...
|
|
758
|
-
@overload
|
|
759
|
-
def is_readable(self: _CheckAnyValue[_PathLike]) -> AssertionOutcome: ...
|
|
760
|
-
|
|
761
|
-
@overload
|
|
762
|
-
def is_writable(self: _CheckAnyValue[str] | _CheckAnyValue[pathlib.Path]) -> AssertionOutcome: ...
|
|
763
|
-
@overload
|
|
764
|
-
def is_writable(self: _CheckAnyValue[_PathLike]) -> AssertionOutcome: ...
|
|
765
|
-
|
|
766
|
-
@overload
|
|
767
|
-
def is_executable(self: _CheckAnyValue[str] | _CheckAnyValue[pathlib.Path]) -> AssertionOutcome: ...
|
|
768
|
-
@overload
|
|
769
|
-
def is_executable(self: _CheckAnyValue[_PathLike]) -> AssertionOutcome: ...
|
|
770
|
-
|
|
771
785
|
@overload
|
|
772
786
|
def is_positive(
|
|
773
787
|
self: _CheckAnyValue[bool] | _CheckAnyValue[int] | _CheckAnyValue[float],
|
|
@@ -906,29 +920,33 @@ if TYPE_CHECKING:
|
|
|
906
920
|
@overload
|
|
907
921
|
def contains_key(self: _CheckAnyValue[dict[_K, _V]], *keys: _K | Matcher[_K]) -> AssertionOutcome: ...
|
|
908
922
|
@overload
|
|
909
|
-
def contains_key(self: _CheckAnyValue[
|
|
923
|
+
def contains_key(self: _CheckAnyValue[_Keyed], *keys: _K | Matcher[_K]) -> AssertionOutcome: ...
|
|
910
924
|
|
|
911
925
|
@overload
|
|
912
926
|
def does_not_contain_key(self: _CheckAnyValue[dict[_K, _V]], *keys: _K | Matcher[_K]) -> AssertionOutcome: ...
|
|
913
927
|
@overload
|
|
914
|
-
def does_not_contain_key(self: _CheckAnyValue[
|
|
928
|
+
def does_not_contain_key(self: _CheckAnyValue[_Keyed], *keys: _K | Matcher[_K]) -> AssertionOutcome: ...
|
|
915
929
|
|
|
916
930
|
@overload
|
|
917
931
|
def contains_value(self: _CheckAnyValue[dict[_K, _V]], *values: _V | Matcher[_V]) -> AssertionOutcome: ...
|
|
918
932
|
@overload
|
|
919
|
-
def contains_value(self: _CheckAnyValue[
|
|
933
|
+
def contains_value(self: _CheckAnyValue[_KeyedWithValues], *values: _V | Matcher[_V]) -> AssertionOutcome: ...
|
|
920
934
|
|
|
921
935
|
@overload
|
|
922
936
|
def does_not_contain_value(
|
|
923
937
|
self: _CheckAnyValue[dict[_K, _V]], *values: _V | Matcher[_V]
|
|
924
938
|
) -> AssertionOutcome: ...
|
|
925
939
|
@overload
|
|
926
|
-
def does_not_contain_value(
|
|
940
|
+
def does_not_contain_value(
|
|
941
|
+
self: _CheckAnyValue[_KeyedWithValues], *values: _V | Matcher[_V]
|
|
942
|
+
) -> AssertionOutcome: ...
|
|
927
943
|
|
|
928
944
|
@overload
|
|
929
945
|
def contains_entry(self: _CheckAnyValue[dict[_K, _V]], *args: object, **kwargs: object) -> AssertionOutcome: ...
|
|
930
946
|
@overload
|
|
931
|
-
def contains_entry(
|
|
947
|
+
def contains_entry(
|
|
948
|
+
self: _CheckAnyValue[_KeyedWithItems], *args: object, **kwargs: object
|
|
949
|
+
) -> AssertionOutcome: ...
|
|
932
950
|
|
|
933
951
|
@overload
|
|
934
952
|
def does_not_contain_entry(
|
|
@@ -936,7 +954,7 @@ if TYPE_CHECKING:
|
|
|
936
954
|
) -> AssertionOutcome: ...
|
|
937
955
|
@overload
|
|
938
956
|
def does_not_contain_entry(
|
|
939
|
-
self: _CheckAnyValue[
|
|
957
|
+
self: _CheckAnyValue[_KeyedWithItems], *args: object, **kwargs: object
|
|
940
958
|
) -> AssertionOutcome: ...
|
|
941
959
|
|
|
942
960
|
@overload
|