assertpy2 2.24.0__tar.gz → 2.25.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.25.0}/PKG-INFO +27 -17
- {assertpy2-2.24.0 → assertpy2-2.25.0}/README.md +24 -14
- {assertpy2-2.24.0 → assertpy2-2.25.0}/assertpy2/_engine/_builder_check_typing.py +11 -7
- {assertpy2-2.24.0 → assertpy2-2.25.0}/assertpy2/_engine/_capable_typing.py +37 -6
- {assertpy2-2.24.0 → assertpy2-2.25.0}/assertpy2/_engine/_check_typing.py +1 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/assertpy2/_engine/_diff.py +49 -38
- {assertpy2-2.24.0 → assertpy2-2.25.0}/assertpy2/_engine/_equality.py +108 -3
- {assertpy2-2.24.0 → assertpy2-2.25.0}/assertpy2/_engine/_introspection.py +44 -7
- {assertpy2-2.24.0 → assertpy2-2.25.0}/assertpy2/_engine/_poll_typing.py +19 -13
- {assertpy2-2.24.0 → assertpy2-2.25.0}/assertpy2/_engine/_typing.py +21 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/assertpy2/_matcher_impls.py +88 -3
- {assertpy2-2.24.0 → assertpy2-2.25.0}/assertpy2/assertpy.py +1 -1
- {assertpy2-2.24.0 → assertpy2-2.25.0}/assertpy2/base.py +7 -3
- {assertpy2-2.24.0 → assertpy2-2.25.0}/assertpy2/exception.py +212 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/assertpy2/helpers.py +82 -37
- {assertpy2-2.24.0 → assertpy2-2.25.0}/benchmarks/test_perf.py +31 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/docs/concepts/stability.md +18 -2
- {assertpy2-2.24.0 → assertpy2-2.25.0}/docs/extending/custom-assertions.md +51 -2
- {assertpy2-2.24.0 → assertpy2-2.25.0}/docs/guides/assertions.md +10 -1
- {assertpy2-2.24.0 → assertpy2-2.25.0}/docs/guides/errors.md +26 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/docs/guides/matchers.md +14 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/docs/guides/testing.md +5 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/docs/recipes.md +29 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/pyproject.toml +22 -7
- {assertpy2-2.24.0 → assertpy2-2.25.0}/scripts/generate_check_protocols.py +17 -2
- {assertpy2-2.24.0 → assertpy2-2.25.0}/scripts/generate_poll_protocols.py +48 -4
- assertpy2-2.25.0/scripts/regenerate.py +45 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/api_snapshot.json +344 -5
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/api_surface.py +30 -5
- assertpy2-2.25.0/tests/test_architecture_doc.py +112 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_async_predicates.py +38 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_capable_protocol.py +52 -1
- assertpy2-2.25.0/tests/test_complexity.py +120 -0
- assertpy2-2.25.0/tests/test_custom_dict.py +1010 -0
- assertpy2-2.25.0/tests/test_declared_floors.py +190 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_expected_contract.py +90 -1
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_expected_exception.py +201 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_matchers.py +117 -9
- assertpy2-2.25.0/tests/test_pin_coverage.py +480 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_poll_protocols.py +2 -1
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_property_based.py +184 -35
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_typing.py +204 -1
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/typing_cases.py +16 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/typing_negative_baseline.py +7 -0
- 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 → assertpy2-2.25.0}/.gitignore +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/CONTRIBUTING.md +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/LICENSE +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/SECURITY.md +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/assertpy2/__init__.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/assertpy2/_clustering.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/assertpy2/_dangling.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/assertpy2/_engine/__init__.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/assertpy2/_engine/_compare.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/assertpy2/_engine/_compat.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/assertpy2/_engine/_contract.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/assertpy2/_engine/_membership.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/assertpy2/_engine/_mixin_base.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/assertpy2/_engine/_operations.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/assertpy2/_engine/_ordering.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/assertpy2/_engine/_path.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/assertpy2/_engine/_require.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/assertpy2/_engine/_size.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/assertpy2/_engine/_text.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/assertpy2/_hints.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/assertpy2/_inline.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/assertpy2/_satisfies.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/assertpy2/_snapshot_codec.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/assertpy2/async_assertions.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/assertpy2/behave_matchers.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/assertpy2/bytes_mixin.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/assertpy2/collection.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/assertpy2/contains.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/assertpy2/dataframe.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/assertpy2/date.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/assertpy2/dict.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/assertpy2/dynamic.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/assertpy2/errors.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/assertpy2/extracting.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/assertpy2/file.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/assertpy2/http_mixin.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/assertpy2/json_mixin.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/assertpy2/matchers.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/assertpy2/numeric.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/assertpy2/outcome.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/assertpy2/py.typed +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/assertpy2/pytest_plugin.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/assertpy2/snapshot.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/assertpy2/string.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/assertpy2/warning.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/docs/assets/diff-equal.png +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/docs/assets/diff-equal.svg +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/docs/assets/diff-gallery.png +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/docs/assets/diff-match.svg +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/docs/assets/diff-sequence.svg +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/docs/assets/diff-set.svg +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/docs/assets/diff-string.svg +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/docs/concepts/type-safety.md +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/docs/extending/integrations.md +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/docs/getting-started/comparison.md +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/docs/getting-started/migration.md +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/docs/getting-started/quickstart.md +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/docs/guides/data.md +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/docs/guides/fluent.md +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/docs/index.md +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/docs/logo-dark.svg +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/docs/logo.svg +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/docs/reference/async.md +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/docs/reference/bytes.md +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/docs/reference/collections.md +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/docs/reference/containment.md +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/docs/reference/core.md +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/docs/reference/dataframes.md +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/docs/reference/dates.md +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/docs/reference/dicts.md +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/docs/reference/dynamic.md +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/docs/reference/entry-points.md +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/docs/reference/errors.md +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/docs/reference/exceptions.md +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/docs/reference/extracting.md +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/docs/reference/files.md +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/docs/reference/http.md +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/docs/reference/json.md +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/docs/reference/matchers.md +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/docs/reference/numbers.md +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/docs/reference/overview.md +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/docs/reference/snapshots.md +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/docs/reference/strings.md +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/docs/reference/warnings.md +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/docs/stylesheets/extra.css +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/mkdocs.yml +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/mkdocs_hooks.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/scripts/mutation_report.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/conftest.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/docs_fixtures.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/group_compat.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/pyright_baseline.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_api_compatibility.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_api_vocabulary.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_async.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_attrs_support.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_behave_matchers.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_bool.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_boundary_cases.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_bytes.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_callable.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_chaining.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_check.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_check_protocols.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_class.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_clustering.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_collection.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_core.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_custom_list.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_dangling.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_dataframe.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_datetime.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_description.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_dict.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_dict_compare.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_docs_examples.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_docs_typing.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_dynamic.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_equals.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_errors.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_evaluation_core.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_exception_context.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_expected_warning.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_extensions.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_extracting.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_fail.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_file.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_grouped_soft.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_hints.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_http.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_in.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_inline_record.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_inline_snapshot.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_iterable_cluster.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_json.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_list.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_match_result.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_matcher_parity.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_matcher_registry.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_message_elision.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_namedtuple.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_none.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_not.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_numbers.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_openapi_contract.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_operation_contract.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_optional_integration_contracts.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_overload_order.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_overloads.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_pipeline.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_property_attrs.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_protocol_parity.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_public_surface.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_pyright_baseline.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_pytest_plugin.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_recursive_assertion.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_recursive_compare.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_recursive_compare_attrs.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_recursive_compare_config.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_recursive_compare_pydantic.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_refusals.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_regex_groups.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_rich_diff.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_same_as.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_satisfy.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_skip_guard.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_snapshots.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_soft.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_soft_fail.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_string.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_structural.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_surface_conformance.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_sync_eventually.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_traceback.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_type.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_type_expression_failures.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_typing_claims.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_typing_completeness.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_typing_conformance.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_typing_from_a_wheel.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_typing_http.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_typing_integrations.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_typing_negative.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_vacuity_contract.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/test_warn.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/typing_harness.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/typing_http.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/typing_integrations.py +0 -0
- {assertpy2-2.24.0 → assertpy2-2.25.0}/tests/typing_integrations_baseline.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: assertpy2
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.25.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
|
|
|
@@ -26,7 +26,7 @@ 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 _Callable, _Orderable, _PathLike
|
|
29
|
+
from ._capable_typing import _Callable, _Keyed, _KeyedWithItems, _KeyedWithValues, _Orderable, _PathLike
|
|
30
30
|
from ._introspection import MappingLike
|
|
31
31
|
from ._typing import (
|
|
32
32
|
_U,
|
|
@@ -906,29 +906,33 @@ if TYPE_CHECKING:
|
|
|
906
906
|
@overload
|
|
907
907
|
def contains_key(self: _CheckAnyValue[dict[_K, _V]], *keys: _K | Matcher[_K]) -> AssertionOutcome: ...
|
|
908
908
|
@overload
|
|
909
|
-
def contains_key(self: _CheckAnyValue[
|
|
909
|
+
def contains_key(self: _CheckAnyValue[_Keyed], *keys: _K | Matcher[_K]) -> AssertionOutcome: ...
|
|
910
910
|
|
|
911
911
|
@overload
|
|
912
912
|
def does_not_contain_key(self: _CheckAnyValue[dict[_K, _V]], *keys: _K | Matcher[_K]) -> AssertionOutcome: ...
|
|
913
913
|
@overload
|
|
914
|
-
def does_not_contain_key(self: _CheckAnyValue[
|
|
914
|
+
def does_not_contain_key(self: _CheckAnyValue[_Keyed], *keys: _K | Matcher[_K]) -> AssertionOutcome: ...
|
|
915
915
|
|
|
916
916
|
@overload
|
|
917
917
|
def contains_value(self: _CheckAnyValue[dict[_K, _V]], *values: _V | Matcher[_V]) -> AssertionOutcome: ...
|
|
918
918
|
@overload
|
|
919
|
-
def contains_value(self: _CheckAnyValue[
|
|
919
|
+
def contains_value(self: _CheckAnyValue[_KeyedWithValues], *values: _V | Matcher[_V]) -> AssertionOutcome: ...
|
|
920
920
|
|
|
921
921
|
@overload
|
|
922
922
|
def does_not_contain_value(
|
|
923
923
|
self: _CheckAnyValue[dict[_K, _V]], *values: _V | Matcher[_V]
|
|
924
924
|
) -> AssertionOutcome: ...
|
|
925
925
|
@overload
|
|
926
|
-
def does_not_contain_value(
|
|
926
|
+
def does_not_contain_value(
|
|
927
|
+
self: _CheckAnyValue[_KeyedWithValues], *values: _V | Matcher[_V]
|
|
928
|
+
) -> AssertionOutcome: ...
|
|
927
929
|
|
|
928
930
|
@overload
|
|
929
931
|
def contains_entry(self: _CheckAnyValue[dict[_K, _V]], *args: object, **kwargs: object) -> AssertionOutcome: ...
|
|
930
932
|
@overload
|
|
931
|
-
def contains_entry(
|
|
933
|
+
def contains_entry(
|
|
934
|
+
self: _CheckAnyValue[_KeyedWithItems], *args: object, **kwargs: object
|
|
935
|
+
) -> AssertionOutcome: ...
|
|
932
936
|
|
|
933
937
|
@overload
|
|
934
938
|
def does_not_contain_entry(
|
|
@@ -936,7 +940,7 @@ if TYPE_CHECKING:
|
|
|
936
940
|
) -> AssertionOutcome: ...
|
|
937
941
|
@overload
|
|
938
942
|
def does_not_contain_entry(
|
|
939
|
-
self: _CheckAnyValue[
|
|
943
|
+
self: _CheckAnyValue[_KeyedWithItems], *args: object, **kwargs: object
|
|
940
944
|
) -> AssertionOutcome: ...
|
|
941
945
|
|
|
942
946
|
@overload
|
|
@@ -49,6 +49,28 @@ if TYPE_CHECKING:
|
|
|
49
49
|
|
|
50
50
|
def __lt__(self, other: Any, /) -> Any: ...
|
|
51
51
|
|
|
52
|
+
class _Keyed(Protocol):
|
|
53
|
+
"""`keys()`, which the dict gate reads before anything else.
|
|
54
|
+
|
|
55
|
+
Iterability is read too and is deliberately NOT here: the gate asks
|
|
56
|
+
`isinstance(value, collections.abc.Iterable)`, which a class earns by registration, and
|
|
57
|
+
requiring `__iter__` structurally refused a registered value that runs. Same blind spot as
|
|
58
|
+
`numbers.Number` in the numeric family, and the same answer.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
def keys(self) -> Any: ...
|
|
62
|
+
|
|
63
|
+
class _KeyedWithItems(_Keyed, Protocol):
|
|
64
|
+
"""And a lookup, which reading an entry needs and reading a key does not."""
|
|
65
|
+
|
|
66
|
+
def __getitem__(self, key: Any, /) -> Any: ...
|
|
67
|
+
|
|
68
|
+
class _KeyedWithValues(_Keyed, Protocol):
|
|
69
|
+
"""And the values, which only the value pair reads. A lookup is not enough: measured on a
|
|
70
|
+
subject carrying `keys` and `__getitem__`, `contains_value` still refuses."""
|
|
71
|
+
|
|
72
|
+
def values(self) -> Any: ...
|
|
73
|
+
|
|
52
74
|
class _PathLike(Protocol):
|
|
53
75
|
"""A value the filesystem assertions can read a path out of, spelled as `os.PathLike` spells it."""
|
|
54
76
|
|
|
@@ -196,18 +218,22 @@ if TYPE_CHECKING:
|
|
|
196
218
|
self: _CapableAssertion[bytes | bytearray], sub: bytes | bytearray
|
|
197
219
|
) -> _CapableAssertion[_CapableT_co]: ...
|
|
198
220
|
def contains_duplicates(self) -> Self: ...
|
|
199
|
-
def contains_entry(
|
|
221
|
+
def contains_entry(
|
|
222
|
+
self: _CapableAssertion[_KeyedWithItems], *args: object, **kwargs: object
|
|
223
|
+
) -> _CapableAssertion[_CapableT_co]: ...
|
|
200
224
|
def contains_error(self, *ex_types: type) -> Self: ...
|
|
201
225
|
def contains_exactly(self, *items: object) -> Self: ...
|
|
202
226
|
def contains_exactly_in_any_order(self, *items: object) -> Self: ...
|
|
203
227
|
def contains_ignoring_case(self, *items: str) -> Self: ...
|
|
204
228
|
def contains_in_order(self, *items: object) -> Self: ...
|
|
205
|
-
def contains_key(self, *keys: object) ->
|
|
229
|
+
def contains_key(self: _CapableAssertion[_Keyed], *keys: object) -> _CapableAssertion[_CapableT_co]: ...
|
|
206
230
|
def contains_none_of(self, *items: str) -> Self: ...
|
|
207
231
|
def contains_only(self, *items: object) -> Self: ...
|
|
208
232
|
def contains_only_once(self, *items: object) -> Self: ...
|
|
209
233
|
def contains_sequence(self, *items: object) -> Self: ...
|
|
210
|
-
def contains_value(
|
|
234
|
+
def contains_value(
|
|
235
|
+
self: _CapableAssertion[_KeyedWithValues], *values: object
|
|
236
|
+
) -> _CapableAssertion[_CapableT_co]: ...
|
|
211
237
|
def decoded_as(
|
|
212
238
|
self: _CapableAssertion[bytes | bytearray], encoding: str = ...
|
|
213
239
|
) -> _CapableAssertion[_CapableT_co]: ...
|
|
@@ -215,10 +241,14 @@ if TYPE_CHECKING:
|
|
|
215
241
|
def described_as(self, description: str) -> Self: ...
|
|
216
242
|
def does_not_contain(self, *items: object) -> Self: ...
|
|
217
243
|
def does_not_contain_duplicates(self) -> Self: ...
|
|
218
|
-
def does_not_contain_entry(
|
|
244
|
+
def does_not_contain_entry(
|
|
245
|
+
self: _CapableAssertion[_KeyedWithItems], *args: object, **kwargs: object
|
|
246
|
+
) -> _CapableAssertion[_CapableT_co]: ...
|
|
219
247
|
def does_not_contain_error(self, *ex_types: type) -> Self: ...
|
|
220
|
-
def does_not_contain_key(self, *keys: object) ->
|
|
221
|
-
def does_not_contain_value(
|
|
248
|
+
def does_not_contain_key(self: _CapableAssertion[_Keyed], *keys: object) -> _CapableAssertion[_CapableT_co]: ...
|
|
249
|
+
def does_not_contain_value(
|
|
250
|
+
self: _CapableAssertion[_KeyedWithValues], *values: object
|
|
251
|
+
) -> _CapableAssertion[_CapableT_co]: ...
|
|
222
252
|
def does_not_exist(self: _CapableAssertion[_PathLike]) -> _CapableAssertion[_CapableT_co]: ...
|
|
223
253
|
def does_not_have_json_path(self, path: str) -> Self: ...
|
|
224
254
|
def does_not_match(self: _CapableAssertion[str], pattern: str) -> _CapableAssertion[_CapableT_co]: ...
|
|
@@ -355,6 +385,7 @@ if TYPE_CHECKING:
|
|
|
355
385
|
def is_zero(self) -> Self: ...
|
|
356
386
|
def matches(self: _CapableAssertion[str], pattern: str) -> _CapableAssertion[_CapableT_co]: ...
|
|
357
387
|
def matches_contract_snapshot(self, id: str | None = ..., path: str = ...) -> Self: ...
|
|
388
|
+
def matches_error_tree(self, *expected: type | list[Any]) -> Self: ...
|
|
358
389
|
def matches_inline(
|
|
359
390
|
self,
|
|
360
391
|
expected: object = ...,
|
|
@@ -675,6 +675,7 @@ if TYPE_CHECKING:
|
|
|
675
675
|
def does_not_contain_error(self, *ex_types: type) -> AssertionOutcome: ...
|
|
676
676
|
def has_root_cause(self, ex: type) -> AssertionOutcome: ...
|
|
677
677
|
def contains_error(self, *ex_types: type) -> AssertionOutcome: ...
|
|
678
|
+
def matches_error_tree(self, *expected: type | list[Any]) -> AssertionOutcome: ...
|
|
678
679
|
@property
|
|
679
680
|
def not_(self) -> Self: ...
|
|
680
681
|
|
|
@@ -33,7 +33,7 @@ from typing import TYPE_CHECKING, TypeVar
|
|
|
33
33
|
|
|
34
34
|
from ..errors import DiffEntry, DiffResult, _safe_repr
|
|
35
35
|
from ._compare import _guarded_not_equal, _node_decision
|
|
36
|
-
from ._introspection import is_attrs_instance, is_mapping_like, is_model_dump_object, is_namedtuple
|
|
36
|
+
from ._introspection import is_attrs_instance, is_mapping_like, is_model_dump_object, is_namedtuple, keyed_snapshot
|
|
37
37
|
from ._path import _ROOT, _Path
|
|
38
38
|
|
|
39
39
|
if TYPE_CHECKING:
|
|
@@ -480,6 +480,48 @@ def _build_equality_diff(
|
|
|
480
480
|
return DiffResult(kind="scalar", entries=[_prefix.leaf_entry(actual=actual, expected=expected)])
|
|
481
481
|
|
|
482
482
|
|
|
483
|
+
def _mapping_diff_entries(actual, expected, prefix: _Path, child_seen: set[int], config) -> list[DiffEntry] | None:
|
|
484
|
+
"""Path-level entries between two mappings, or ``None`` when either cannot be walked by key.
|
|
485
|
+
|
|
486
|
+
Both sides are snapshotted first, so the keys walked below are the ones that were proved readable
|
|
487
|
+
rather than a second reading of a value free to answer differently between passes.
|
|
488
|
+
"""
|
|
489
|
+
kept, kept_expected = keyed_snapshot(actual), keyed_snapshot(expected)
|
|
490
|
+
if kept is None or kept_expected is None:
|
|
491
|
+
return None
|
|
492
|
+
entries: list[DiffEntry] = []
|
|
493
|
+
actual_keys = set(kept)
|
|
494
|
+
expected_keys = set(kept_expected)
|
|
495
|
+
if config is not None and config.strict_types:
|
|
496
|
+
# `{True} & {1}` hands back whichever side the set drew from, losing the type that differs
|
|
497
|
+
stored = {key: key for key in kept_expected}
|
|
498
|
+
for key in kept:
|
|
499
|
+
counterpart = stored.get(key, key)
|
|
500
|
+
if type(key) is not type(counterpart):
|
|
501
|
+
entries.append(prefix.key(key).entry(actual=key, expected=counterpart))
|
|
502
|
+
for key in _ordered_keys(kept, kept_expected):
|
|
503
|
+
if key not in expected_keys:
|
|
504
|
+
entries.append(prefix.key(key).entry(actual=kept[key], expected=None, absent="expected"))
|
|
505
|
+
elif key not in actual_keys:
|
|
506
|
+
entries.append(prefix.key(key).entry(actual=None, absent="actual", expected=kept_expected[key]))
|
|
507
|
+
else:
|
|
508
|
+
decision = _node_decision(kept[key], kept_expected[key], config, field=key)
|
|
509
|
+
if decision == "leaf":
|
|
510
|
+
entries.append(prefix.key(key).entry(actual=kept[key], expected=kept_expected[key]))
|
|
511
|
+
elif decision != "equal":
|
|
512
|
+
entries.extend(
|
|
513
|
+
_child_entries(
|
|
514
|
+
kept[key],
|
|
515
|
+
kept_expected[key],
|
|
516
|
+
prefix.key(key),
|
|
517
|
+
descended_for=decision,
|
|
518
|
+
_seen=child_seen,
|
|
519
|
+
config=config,
|
|
520
|
+
)
|
|
521
|
+
)
|
|
522
|
+
return entries
|
|
523
|
+
|
|
524
|
+
|
|
483
525
|
def _sub_diff_entries(
|
|
484
526
|
actual: object, expected: object, prefix: _Path = _ROOT, *, _seen: set[int] | None = None, config=None
|
|
485
527
|
) -> list[DiffEntry] | None:
|
|
@@ -500,38 +542,7 @@ def _sub_diff_entries(
|
|
|
500
542
|
return [prefix.entry(actual="<circular ref>", expected="<circular ref>")]
|
|
501
543
|
|
|
502
544
|
if is_mapping_like(actual) and is_mapping_like(expected):
|
|
503
|
-
|
|
504
|
-
entries: list[DiffEntry] = []
|
|
505
|
-
actual_keys = set(actual)
|
|
506
|
-
expected_keys = set(expected)
|
|
507
|
-
if config is not None and config.strict_types:
|
|
508
|
-
# `{True} & {1}` hands back whichever side the set drew from, losing the type that differs
|
|
509
|
-
stored = {key: key for key in expected}
|
|
510
|
-
for key in actual:
|
|
511
|
-
counterpart = stored.get(key, key)
|
|
512
|
-
if type(key) is not type(counterpart):
|
|
513
|
-
entries.append(prefix.key(key).entry(actual=key, expected=counterpart))
|
|
514
|
-
for key in _ordered_keys(actual, expected):
|
|
515
|
-
if key not in expected_keys:
|
|
516
|
-
entries.append(prefix.key(key).entry(actual=actual[key], expected=None, absent="expected"))
|
|
517
|
-
elif key not in actual_keys:
|
|
518
|
-
entries.append(prefix.key(key).entry(actual=None, absent="actual", expected=expected[key]))
|
|
519
|
-
else:
|
|
520
|
-
decision = _node_decision(actual[key], expected[key], config, field=key)
|
|
521
|
-
if decision == "leaf":
|
|
522
|
-
entries.append(prefix.key(key).entry(actual=actual[key], expected=expected[key]))
|
|
523
|
-
elif decision != "equal":
|
|
524
|
-
entries.extend(
|
|
525
|
-
_child_entries(
|
|
526
|
-
actual[key],
|
|
527
|
-
expected[key],
|
|
528
|
-
prefix.key(key),
|
|
529
|
-
descended_for=decision,
|
|
530
|
-
_seen=child_seen,
|
|
531
|
-
config=config,
|
|
532
|
-
)
|
|
533
|
-
)
|
|
534
|
-
return entries
|
|
545
|
+
return _mapping_diff_entries(actual, expected, prefix, _seen | {id(actual), id(expected)}, config)
|
|
535
546
|
if (
|
|
536
547
|
dataclasses.is_dataclass(actual)
|
|
537
548
|
and not isinstance(actual, type)
|
|
@@ -563,11 +574,11 @@ def _sub_diff_entries(
|
|
|
563
574
|
config=config,
|
|
564
575
|
)
|
|
565
576
|
)
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
577
|
+
entries.extend(
|
|
578
|
+
prefix.attr(field_name).entry(actual=None, absent="actual", expected=getattr(expected, field_name))
|
|
579
|
+
for field_name in expected._fields
|
|
580
|
+
if field_name not in actual._fields # _fields, not hasattr (count/index collide)
|
|
581
|
+
)
|
|
571
582
|
return entries
|
|
572
583
|
both_model = is_model_dump_object(actual) and is_model_dump_object(expected)
|
|
573
584
|
both_attrs = is_attrs_instance(actual) and is_attrs_instance(expected)
|