assertpy 1.1__tar.gz → 2.35.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.
- assertpy-2.35.0/.gitignore +34 -0
- assertpy-2.35.0/CONTRIBUTING.md +148 -0
- {assertpy-1.1 → assertpy-2.35.0}/LICENSE +2 -1
- assertpy-2.35.0/PKG-INFO +324 -0
- assertpy-2.35.0/README.md +271 -0
- assertpy-2.35.0/SECURITY.md +24 -0
- assertpy-2.35.0/assertpy/__init__.py +87 -0
- assertpy-2.35.0/assertpy/_clustering.py +511 -0
- assertpy-2.35.0/assertpy/_dangling.py +505 -0
- assertpy-2.35.0/assertpy/_engine/__init__.py +1 -0
- assertpy-2.35.0/assertpy/_engine/_builder_check_typing.py +1365 -0
- assertpy-2.35.0/assertpy/_engine/_capable_typing.py +772 -0
- assertpy-2.35.0/assertpy/_engine/_check_typing.py +753 -0
- assertpy-2.35.0/assertpy/_engine/_compare.py +983 -0
- assertpy-2.35.0/assertpy/_engine/_compat.py +38 -0
- assertpy-2.35.0/assertpy/_engine/_contract.py +1926 -0
- assertpy-2.35.0/assertpy/_engine/_diff.py +1058 -0
- assertpy-2.35.0/assertpy/_engine/_equality.py +843 -0
- assertpy-2.35.0/assertpy/_engine/_introspection.py +729 -0
- assertpy-2.35.0/assertpy/_engine/_membership.py +522 -0
- assertpy-2.35.0/assertpy/_engine/_mixin_base.py +258 -0
- assertpy-2.35.0/assertpy/_engine/_negated_typing.py +2176 -0
- assertpy-2.35.0/assertpy/_engine/_operations.py +75 -0
- assertpy-2.35.0/assertpy/_engine/_ordering.py +884 -0
- assertpy-2.35.0/assertpy/_engine/_pairing.py +70 -0
- assertpy-2.35.0/assertpy/_engine/_path.py +158 -0
- assertpy-2.35.0/assertpy/_engine/_poll_typing.py +6039 -0
- assertpy-2.35.0/assertpy/_engine/_require.py +312 -0
- assertpy-2.35.0/assertpy/_engine/_size.py +31 -0
- assertpy-2.35.0/assertpy/_engine/_text.py +39 -0
- assertpy-2.35.0/assertpy/_engine/_typing.py +1226 -0
- assertpy-2.35.0/assertpy/_hints.py +1213 -0
- assertpy-2.35.0/assertpy/_inline.py +484 -0
- assertpy-2.35.0/assertpy/_matcher_impls.py +1876 -0
- assertpy-2.35.0/assertpy/_passes.py +249 -0
- assertpy-2.35.0/assertpy/_renamed.py +64 -0
- assertpy-2.35.0/assertpy/_satisfies.py +835 -0
- assertpy-2.35.0/assertpy/_snapshot_codec.py +290 -0
- assertpy-2.35.0/assertpy/assertpy.py +2640 -0
- assertpy-2.35.0/assertpy/async_assertions.py +864 -0
- assertpy-2.35.0/assertpy/base.py +862 -0
- assertpy-2.35.0/assertpy/behave_matchers.py +96 -0
- assertpy-2.35.0/assertpy/bytes_mixin.py +158 -0
- assertpy-2.35.0/assertpy/collection.py +528 -0
- assertpy-2.35.0/assertpy/contains.py +1068 -0
- assertpy-2.35.0/assertpy/dataframe.py +200 -0
- assertpy-2.35.0/assertpy/date.py +327 -0
- assertpy-2.35.0/assertpy/dict.py +249 -0
- assertpy-2.35.0/assertpy/dynamic.py +222 -0
- assertpy-2.35.0/assertpy/errors.py +782 -0
- assertpy-2.35.0/assertpy/exception.py +838 -0
- assertpy-2.35.0/assertpy/extracting.py +266 -0
- assertpy-2.35.0/assertpy/file.py +324 -0
- assertpy-2.35.0/assertpy/helpers.py +747 -0
- assertpy-2.35.0/assertpy/http_mixin.py +218 -0
- assertpy-2.35.0/assertpy/json_mixin.py +1265 -0
- assertpy-2.35.0/assertpy/matchers.py +639 -0
- assertpy-2.35.0/assertpy/numeric.py +696 -0
- assertpy-2.35.0/assertpy/outcome.py +163 -0
- assertpy-2.35.0/assertpy/py.typed +0 -0
- assertpy-2.35.0/assertpy/pytest_plugin.py +1650 -0
- assertpy-2.35.0/assertpy/snapshot.py +1031 -0
- assertpy-2.35.0/assertpy/string.py +803 -0
- assertpy-2.35.0/assertpy/warning.py +137 -0
- assertpy-2.35.0/benchmarks/test_perf.py +492 -0
- assertpy-2.35.0/bridge/LICENSE +28 -0
- assertpy-2.35.0/bridge/README.md +17 -0
- assertpy-2.35.0/docs/assets/diff-equal.png +0 -0
- assertpy-2.35.0/docs/assets/diff-equal.svg +51 -0
- assertpy-2.35.0/docs/assets/diff-gallery.png +0 -0
- assertpy-2.35.0/docs/assets/diff-match.svg +50 -0
- assertpy-2.35.0/docs/assets/diff-sequence.svg +51 -0
- assertpy-2.35.0/docs/assets/diff-set.svg +47 -0
- assertpy-2.35.0/docs/assets/diff-string.svg +59 -0
- assertpy-2.35.0/docs/concepts/stability.md +190 -0
- assertpy-2.35.0/docs/concepts/type-safety.md +572 -0
- assertpy-2.35.0/docs/extending/custom-assertions.md +186 -0
- assertpy-2.35.0/docs/extending/integrations.md +487 -0
- assertpy-2.35.0/docs/getting-started/comparison.md +263 -0
- assertpy-2.35.0/docs/getting-started/migration.md +155 -0
- assertpy-2.35.0/docs/getting-started/quickstart.md +86 -0
- assertpy-2.35.0/docs/guides/assertions.md +1109 -0
- assertpy-2.35.0/docs/guides/data.md +254 -0
- assertpy-2.35.0/docs/guides/errors.md +1370 -0
- assertpy-2.35.0/docs/guides/fluent.md +199 -0
- assertpy-2.35.0/docs/guides/matchers.md +533 -0
- assertpy-2.35.0/docs/guides/testing.md +648 -0
- assertpy-2.35.0/docs/index.md +189 -0
- assertpy-2.35.0/docs/logo-dark.svg +5 -0
- assertpy-2.35.0/docs/logo.svg +5 -0
- assertpy-2.35.0/docs/recipes.md +324 -0
- assertpy-2.35.0/docs/reference/async.md +30 -0
- assertpy-2.35.0/docs/reference/bytes.md +5 -0
- assertpy-2.35.0/docs/reference/collections.md +5 -0
- assertpy-2.35.0/docs/reference/containment.md +5 -0
- assertpy-2.35.0/docs/reference/core.md +12 -0
- assertpy-2.35.0/docs/reference/dataframes.md +22 -0
- assertpy-2.35.0/docs/reference/dates.md +5 -0
- assertpy-2.35.0/docs/reference/dicts.md +5 -0
- assertpy-2.35.0/docs/reference/dynamic.md +6 -0
- assertpy-2.35.0/docs/reference/entry-points.md +105 -0
- assertpy-2.35.0/docs/reference/errors.md +54 -0
- assertpy-2.35.0/docs/reference/exceptions.md +5 -0
- assertpy-2.35.0/docs/reference/extracting.md +5 -0
- assertpy-2.35.0/docs/reference/files.md +11 -0
- assertpy-2.35.0/docs/reference/http.md +6 -0
- assertpy-2.35.0/docs/reference/json.md +5 -0
- assertpy-2.35.0/docs/reference/matchers.md +45 -0
- assertpy-2.35.0/docs/reference/numbers.md +5 -0
- assertpy-2.35.0/docs/reference/overview.md +31 -0
- assertpy-2.35.0/docs/reference/snapshots.md +29 -0
- assertpy-2.35.0/docs/reference/strings.md +6 -0
- assertpy-2.35.0/docs/reference/warnings.md +5 -0
- assertpy-2.35.0/docs/stylesheets/extra.css +56 -0
- assertpy-2.35.0/mkdocs.yml +164 -0
- assertpy-2.35.0/mkdocs_hooks.py +16 -0
- assertpy-2.35.0/pyproject.toml +434 -0
- assertpy-2.35.0/scripts/generate_check_protocols.py +169 -0
- assertpy-2.35.0/scripts/generate_poll_protocols.py +1748 -0
- assertpy-2.35.0/scripts/mutation_report.py +145 -0
- assertpy-2.35.0/scripts/regenerate.py +49 -0
- assertpy-2.35.0/tests/api_snapshot.json +7197 -0
- assertpy-2.35.0/tests/api_surface.py +514 -0
- assertpy-2.35.0/tests/chain_corpus.py +337 -0
- assertpy-2.35.0/tests/chain_model.py +633 -0
- assertpy-2.35.0/tests/chain_surfaces.py +226 -0
- assertpy-2.35.0/tests/conftest.py +111 -0
- assertpy-2.35.0/tests/data/json_schema_test_suite/LICENSE +19 -0
- assertpy-2.35.0/tests/data/json_schema_test_suite/date-time.json +226 -0
- assertpy-2.35.0/tests/data/json_schema_test_suite/date.json +423 -0
- assertpy-2.35.0/tests/data/json_schema_test_suite/duration.json +273 -0
- assertpy-2.35.0/tests/data/json_schema_test_suite/email.json +368 -0
- assertpy-2.35.0/tests/data/json_schema_test_suite/hostname.json +376 -0
- assertpy-2.35.0/tests/data/json_schema_test_suite/multipleOf.json +102 -0
- assertpy-2.35.0/tests/data/json_schema_test_suite/time.json +297 -0
- assertpy-2.35.0/tests/data/json_schema_test_suite/uri.json +248 -0
- assertpy-2.35.0/tests/docs_fixtures.py +251 -0
- assertpy-2.35.0/tests/format_corpus.py +137 -0
- assertpy-2.35.0/tests/group_compat.py +31 -0
- assertpy-2.35.0/tests/poll_parity_baseline.py +919 -0
- assertpy-2.35.0/tests/poll_parity_cases.py +3331 -0
- assertpy-2.35.0/tests/pyright_baseline.py +114 -0
- assertpy-2.35.0/tests/test_api_compatibility.py +471 -0
- assertpy-2.35.0/tests/test_api_vocabulary.py +147 -0
- assertpy-2.35.0/tests/test_architecture_doc.py +107 -0
- assertpy-2.35.0/tests/test_async.py +1705 -0
- assertpy-2.35.0/tests/test_async_predicates.py +195 -0
- assertpy-2.35.0/tests/test_attrs_support.py +480 -0
- assertpy-2.35.0/tests/test_behave_matchers.py +111 -0
- assertpy-2.35.0/tests/test_bool.py +33 -0
- assertpy-2.35.0/tests/test_boundary_cases.py +112 -0
- assertpy-2.35.0/tests/test_bytes.py +243 -0
- assertpy-2.35.0/tests/test_call_awaitables.py +163 -0
- assertpy-2.35.0/tests/test_callable.py +48 -0
- assertpy-2.35.0/tests/test_capable_protocol.py +1076 -0
- assertpy-2.35.0/tests/test_chain_machine.py +664 -0
- assertpy-2.35.0/tests/test_chaining.py +22 -0
- assertpy-2.35.0/tests/test_check.py +351 -0
- assertpy-2.35.0/tests/test_check_protocols.py +230 -0
- assertpy-2.35.0/tests/test_class.py +145 -0
- assertpy-2.35.0/tests/test_class_in_spec.py +293 -0
- assertpy-2.35.0/tests/test_clustering.py +1587 -0
- assertpy-2.35.0/tests/test_collection.py +321 -0
- assertpy-2.35.0/tests/test_compiled_patterns.py +209 -0
- assertpy-2.35.0/tests/test_complexity.py +124 -0
- assertpy-2.35.0/tests/test_core.py +194 -0
- assertpy-2.35.0/tests/test_custom_dict.py +1013 -0
- assertpy-2.35.0/tests/test_custom_list.py +47 -0
- assertpy-2.35.0/tests/test_dangling.py +1196 -0
- assertpy-2.35.0/tests/test_dataframe.py +299 -0
- assertpy-2.35.0/tests/test_datetime.py +1024 -0
- assertpy-2.35.0/tests/test_declared_floors.py +190 -0
- assertpy-2.35.0/tests/test_deep_nesting.py +438 -0
- assertpy-2.35.0/tests/test_description.py +29 -0
- assertpy-2.35.0/tests/test_dict.py +422 -0
- assertpy-2.35.0/tests/test_dict_compare.py +1086 -0
- assertpy-2.35.0/tests/test_diff_laws.py +1772 -0
- assertpy-2.35.0/tests/test_difference_places.py +223 -0
- assertpy-2.35.0/tests/test_docs_examples.py +334 -0
- assertpy-2.35.0/tests/test_docs_typing.py +122 -0
- assertpy-2.35.0/tests/test_duality.py +1018 -0
- assertpy-2.35.0/tests/test_dynamic.py +460 -0
- assertpy-2.35.0/tests/test_equality_signals.py +1206 -0
- assertpy-2.35.0/tests/test_equals.py +487 -0
- assertpy-2.35.0/tests/test_errors.py +464 -0
- assertpy-2.35.0/tests/test_evaluation_core.py +3291 -0
- assertpy-2.35.0/tests/test_exact_oracle.py +180 -0
- assertpy-2.35.0/tests/test_exception_context.py +90 -0
- assertpy-2.35.0/tests/test_exception_fields.py +267 -0
- assertpy-2.35.0/tests/test_expected_contract.py +303 -0
- assertpy-2.35.0/tests/test_expected_exception.py +1305 -0
- assertpy-2.35.0/tests/test_expected_warning.py +288 -0
- assertpy-2.35.0/tests/test_extensions.py +639 -0
- assertpy-2.35.0/tests/test_extracting.py +494 -0
- assertpy-2.35.0/tests/test_fail.py +15 -0
- assertpy-2.35.0/tests/test_file.py +297 -0
- assertpy-2.35.0/tests/test_fixed_width_integers.py +717 -0
- assertpy-2.35.0/tests/test_grouped_soft.py +148 -0
- assertpy-2.35.0/tests/test_hints.py +1025 -0
- assertpy-2.35.0/tests/test_hostile_values.py +238 -0
- assertpy-2.35.0/tests/test_http.py +599 -0
- assertpy-2.35.0/tests/test_import_cost.py +44 -0
- assertpy-2.35.0/tests/test_in.py +66 -0
- assertpy-2.35.0/tests/test_inline_record.py +1075 -0
- assertpy-2.35.0/tests/test_inline_snapshot.py +280 -0
- assertpy-2.35.0/tests/test_integer_arguments.py +113 -0
- assertpy-2.35.0/tests/test_iterable_cluster.py +426 -0
- assertpy-2.35.0/tests/test_json.py +209 -0
- assertpy-2.35.0/tests/test_list.py +1012 -0
- assertpy-2.35.0/tests/test_log_passes.py +1957 -0
- assertpy-2.35.0/tests/test_match_result.py +256 -0
- assertpy-2.35.0/tests/test_matcher_algebra.py +230 -0
- assertpy-2.35.0/tests/test_matcher_parity.py +204 -0
- assertpy-2.35.0/tests/test_matcher_registry.py +292 -0
- assertpy-2.35.0/tests/test_matchers.py +1630 -0
- assertpy-2.35.0/tests/test_membership_failures.py +270 -0
- assertpy-2.35.0/tests/test_membership_family.py +523 -0
- assertpy-2.35.0/tests/test_message_elision.py +278 -0
- assertpy-2.35.0/tests/test_message_values.py +345 -0
- assertpy-2.35.0/tests/test_missing_names.py +488 -0
- assertpy-2.35.0/tests/test_moment_relations.py +866 -0
- assertpy-2.35.0/tests/test_namedtuple.py +79 -0
- assertpy-2.35.0/tests/test_negated_protocols.py +422 -0
- assertpy-2.35.0/tests/test_none.py +27 -0
- assertpy-2.35.0/tests/test_not.py +421 -0
- assertpy-2.35.0/tests/test_number_zoo.py +491 -0
- assertpy-2.35.0/tests/test_numbers.py +2208 -0
- assertpy-2.35.0/tests/test_numeric_questions.py +322 -0
- assertpy-2.35.0/tests/test_one_shot_chains.py +572 -0
- assertpy-2.35.0/tests/test_one_shot_matchers.py +421 -0
- assertpy-2.35.0/tests/test_openapi_30_keywords.py +576 -0
- assertpy-2.35.0/tests/test_openapi_contract.py +1048 -0
- assertpy-2.35.0/tests/test_openapi_format_suite.py +118 -0
- assertpy-2.35.0/tests/test_openapi_formats.py +1007 -0
- assertpy-2.35.0/tests/test_openapi_multiple_of.py +322 -0
- assertpy-2.35.0/tests/test_openapi_oracle.py +328 -0
- assertpy-2.35.0/tests/test_openapi_part_read.py +441 -0
- assertpy-2.35.0/tests/test_operation_contract.py +404 -0
- assertpy-2.35.0/tests/test_optional_integration_contracts.py +86 -0
- assertpy-2.35.0/tests/test_overload_order.py +174 -0
- assertpy-2.35.0/tests/test_overloads.py +58 -0
- assertpy-2.35.0/tests/test_pin_coverage.py +784 -0
- assertpy-2.35.0/tests/test_pipeline.py +187 -0
- assertpy-2.35.0/tests/test_poll_parity.py +388 -0
- assertpy-2.35.0/tests/test_poll_protocols.py +489 -0
- assertpy-2.35.0/tests/test_prerequisites.py +376 -0
- assertpy-2.35.0/tests/test_property_attrs.py +123 -0
- assertpy-2.35.0/tests/test_property_based.py +3140 -0
- assertpy-2.35.0/tests/test_protocol_parity.py +698 -0
- assertpy-2.35.0/tests/test_public_surface.py +423 -0
- assertpy-2.35.0/tests/test_pyright_baseline.py +156 -0
- assertpy-2.35.0/tests/test_pytest_plugin.py +4426 -0
- assertpy-2.35.0/tests/test_recursive_assertion.py +227 -0
- assertpy-2.35.0/tests/test_recursive_compare.py +275 -0
- assertpy-2.35.0/tests/test_recursive_compare_attrs.py +81 -0
- assertpy-2.35.0/tests/test_recursive_compare_config.py +1509 -0
- assertpy-2.35.0/tests/test_recursive_compare_pydantic.py +466 -0
- assertpy-2.35.0/tests/test_refusal_frames.py +292 -0
- assertpy-2.35.0/tests/test_refusals.py +440 -0
- assertpy-2.35.0/tests/test_regex_groups.py +114 -0
- assertpy-2.35.0/tests/test_relative_tolerance.py +298 -0
- assertpy-2.35.0/tests/test_renamed.py +196 -0
- assertpy-2.35.0/tests/test_render_laws.py +651 -0
- assertpy-2.35.0/tests/test_requirement.py +673 -0
- assertpy-2.35.0/tests/test_rich_diff.py +2664 -0
- assertpy-2.35.0/tests/test_same_as.py +42 -0
- assertpy-2.35.0/tests/test_satisfy.py +651 -0
- assertpy-2.35.0/tests/test_skip_guard.py +240 -0
- assertpy-2.35.0/tests/test_snapshots.py +1975 -0
- assertpy-2.35.0/tests/test_soft.py +645 -0
- assertpy-2.35.0/tests/test_soft_fail.py +61 -0
- assertpy-2.35.0/tests/test_soft_summary_blocks.py +356 -0
- assertpy-2.35.0/tests/test_strict_reach.py +466 -0
- assertpy-2.35.0/tests/test_string.py +747 -0
- assertpy-2.35.0/tests/test_structural.py +5395 -0
- assertpy-2.35.0/tests/test_structure_sentence.py +539 -0
- assertpy-2.35.0/tests/test_surface_conformance.py +488 -0
- assertpy-2.35.0/tests/test_sync_eventually.py +312 -0
- assertpy-2.35.0/tests/test_text_relations.py +404 -0
- assertpy-2.35.0/tests/test_traceback.py +84 -0
- assertpy-2.35.0/tests/test_type.py +311 -0
- assertpy-2.35.0/tests/test_type_expression_failures.py +148 -0
- assertpy-2.35.0/tests/test_typing.py +922 -0
- assertpy-2.35.0/tests/test_typing_chain_corpus.py +38 -0
- assertpy-2.35.0/tests/test_typing_claims.py +195 -0
- assertpy-2.35.0/tests/test_typing_completeness.py +142 -0
- assertpy-2.35.0/tests/test_typing_conformance.py +489 -0
- assertpy-2.35.0/tests/test_typing_from_a_wheel.py +97 -0
- assertpy-2.35.0/tests/test_typing_harness.py +79 -0
- assertpy-2.35.0/tests/test_typing_http.py +98 -0
- assertpy-2.35.0/tests/test_typing_integrations.py +110 -0
- assertpy-2.35.0/tests/test_typing_negative.py +208 -0
- assertpy-2.35.0/tests/test_typing_promises.py +76 -0
- assertpy-2.35.0/tests/test_unrenderable_values.py +244 -0
- assertpy-2.35.0/tests/test_vacuity_contract.py +686 -0
- assertpy-2.35.0/tests/test_verdict_corpus.py +137 -0
- assertpy-2.35.0/tests/test_verdicts_held_to_python.py +294 -0
- assertpy-2.35.0/tests/test_warn.py +158 -0
- assertpy-2.35.0/tests/test_warn_on_failure.py +158 -0
- assertpy-2.35.0/tests/typing_cases.py +702 -0
- assertpy-2.35.0/tests/typing_chain_corpus_baseline.json +256 -0
- assertpy-2.35.0/tests/typing_harness.py +166 -0
- assertpy-2.35.0/tests/typing_http.py +81 -0
- assertpy-2.35.0/tests/typing_integrations.py +65 -0
- assertpy-2.35.0/tests/typing_integrations_baseline.py +94 -0
- assertpy-2.35.0/tests/typing_negative_baseline.py +562 -0
- assertpy-2.35.0/tests/typing_promises.py +288 -0
- assertpy-2.35.0/tests/verdict_corpus.py +632 -0
- assertpy-2.35.0/tests/verdict_golden.txt +127 -0
- assertpy-1.1/PKG-INFO +0 -62
- assertpy-1.1/assertpy/__init__.py +0 -3
- assertpy-1.1/assertpy/assertpy.py +0 -433
- assertpy-1.1/assertpy/base.py +0 -427
- assertpy-1.1/assertpy/collection.py +0 -196
- assertpy-1.1/assertpy/contains.py +0 -362
- assertpy-1.1/assertpy/date.py +0 -185
- assertpy-1.1/assertpy/dict.py +0 -236
- assertpy-1.1/assertpy/dynamic.py +0 -116
- assertpy-1.1/assertpy/exception.py +0 -113
- assertpy-1.1/assertpy/extracting.py +0 -231
- assertpy-1.1/assertpy/file.py +0 -225
- assertpy-1.1/assertpy/helpers.py +0 -250
- assertpy-1.1/assertpy/numeric.py +0 -513
- assertpy-1.1/assertpy/snapshot.py +0 -208
- assertpy-1.1/assertpy/string.py +0 -413
- assertpy-1.1/setup.cfg +0 -8
- assertpy-1.1/setup.py +0 -68
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# .gitignore
|
|
2
|
+
*.py[cod]
|
|
3
|
+
.cache
|
|
4
|
+
.pytest_cache
|
|
5
|
+
__pycache__
|
|
6
|
+
|
|
7
|
+
# testing output
|
|
8
|
+
*.xml
|
|
9
|
+
*.log
|
|
10
|
+
cover
|
|
11
|
+
.coverage
|
|
12
|
+
htmlcov
|
|
13
|
+
.hypothesis
|
|
14
|
+
__snapshots
|
|
15
|
+
mycustompath
|
|
16
|
+
|
|
17
|
+
# packaging
|
|
18
|
+
MANIFEST
|
|
19
|
+
dist
|
|
20
|
+
|
|
21
|
+
# mac
|
|
22
|
+
.DS_Store
|
|
23
|
+
|
|
24
|
+
# venv
|
|
25
|
+
.venv
|
|
26
|
+
|
|
27
|
+
# IDE
|
|
28
|
+
/.idea
|
|
29
|
+
.vscode
|
|
30
|
+
|
|
31
|
+
# docs output
|
|
32
|
+
docs/build
|
|
33
|
+
docs/out
|
|
34
|
+
/site/
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
Contributions of docs, tests, or code are welcome.
|
|
4
|
+
|
|
5
|
+
## Workflow
|
|
6
|
+
|
|
7
|
+
1. For a new assertion or matcher, open an issue first. The API grows from demand, and a working
|
|
8
|
+
implementation on its own is not enough to land one
|
|
9
|
+
2. Fork the repo
|
|
10
|
+
3. Clone your fork (`git clone <your_fork_url>`)
|
|
11
|
+
4. Create a branch (`git checkout -b my_branch`)
|
|
12
|
+
5. Install dependencies with the [one sync command](#verification-pipeline) below, not a bare `uv sync`
|
|
13
|
+
6. Make your changes
|
|
14
|
+
7. Run the [verification pipeline](#verification-pipeline) and fix any issues
|
|
15
|
+
8. Commit using [Conventional Commits](#commit-style)
|
|
16
|
+
9. Push your branch (`git push origin my_branch`)
|
|
17
|
+
10. Open a [Pull Request](http://github.com/assertpy/assertpy/pulls)
|
|
18
|
+
|
|
19
|
+
Read more about how pulls work on GitHub's [About pull requests](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-requests) page.
|
|
20
|
+
|
|
21
|
+
## Requirements
|
|
22
|
+
|
|
23
|
+
- Python 3.10+
|
|
24
|
+
- [uv](https://docs.astral.sh/uv/) as the package manager
|
|
25
|
+
|
|
26
|
+
## Before you change a public signature
|
|
27
|
+
|
|
28
|
+
Read [ARCHITECTURE.md](ARCHITECTURE.md). Five files under `assertpy/_engine/` are generated, the public
|
|
29
|
+
surface is pinned by a recorded snapshot, and neither is discoverable from the files themselves.
|
|
30
|
+
|
|
31
|
+
## Verification pipeline
|
|
32
|
+
|
|
33
|
+
Run all checks before submitting a PR. Every step must pass.
|
|
34
|
+
|
|
35
|
+
### Install
|
|
36
|
+
|
|
37
|
+
A bare `uv sync` leaves out the optional libraries several tests import, and those tests then fail
|
|
38
|
+
rather than skip. Install exactly this set:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
uv sync --extra json --extra data --extra inline --group integrations --group docs-examples
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### The main gate
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
uv run ruff check .
|
|
48
|
+
uv run ruff format --check .
|
|
49
|
+
uv run ty check assertpy/ tests/test_typing.py
|
|
50
|
+
uv run pytest --cov=assertpy --cov-fail-under=100 --ignore=tests/test_docs_examples.py tests
|
|
51
|
+
uv run pytest tests/test_docs_examples.py tests/test_typing_claims.py
|
|
52
|
+
uv run --group docs mkdocs build --strict
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
CI requires 100% code coverage.
|
|
56
|
+
|
|
57
|
+
### Then once on the supported floor
|
|
58
|
+
|
|
59
|
+
Run the suite on Python 3.10 as well. This is not optional, and the block above does not cover it.
|
|
60
|
+
|
|
61
|
+
A union carries `__name__` from 3.14 onwards and not before. `logging.LoggerAdapter` became
|
|
62
|
+
subscriptable in 3.11. Neither difference is visible to a type checker told to target 3.10, and both
|
|
63
|
+
shipped as defects that every other gate passed.
|
|
64
|
+
|
|
65
|
+
Use a throwaway environment. `uv run --python 3.10` inside the project rebuilds `.venv` for that
|
|
66
|
+
version, and on Windows it can fail partway and leave you without pytest:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
uv venv /tmp/py310 --python 3.10
|
|
70
|
+
|
|
71
|
+
export VIRTUAL_ENV=/tmp/py310
|
|
72
|
+
uv pip install -e ".[json,data,inline]"
|
|
73
|
+
uv pip install pytest pytest-cov hypothesis ruff
|
|
74
|
+
uv pip install attrs pydantic requests httpx flask
|
|
75
|
+
|
|
76
|
+
/tmp/py310/bin/python -m pytest tests --ignore=tests/test_docs_examples.py -q
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
`ruff` belongs in that list: the tests that compare a generated file against its generator shell out
|
|
80
|
+
to it, and without it they fail for a reason that has nothing to do with your change.
|
|
81
|
+
|
|
82
|
+
### The four type checkers
|
|
83
|
+
|
|
84
|
+
Run these after the main gate, not before. `uv run --group typecheck` adds the checkers to the same
|
|
85
|
+
`.venv` and leaves them there, and a coverage run with them installed collects a different set:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
uv run --group typecheck mypy --strict --follow-imports=silent tests/test_typing.py
|
|
89
|
+
PYRIGHT_PYTHON_FORCE_VERSION=1.1.414 uv run --group typecheck pyright --pythonversion 3.14 tests/test_typing.py
|
|
90
|
+
uv run --group typecheck pyrefly check tests/test_typing.py
|
|
91
|
+
uv run --group typecheck pytest tests/test_pyright_baseline.py
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
`PYRIGHT_PYTHON_FORCE_VERSION` picks the engine rather than the launcher. The `pyright` distribution on
|
|
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.
|
|
99
|
+
|
|
100
|
+
`--pythonversion 3.14` is not decoration. Pyright reports against the interpreter it finds unless
|
|
101
|
+
told otherwise, and the count moves with it: 174 diagnostics for this package on 3.10 against 169 on
|
|
102
|
+
3.14.
|
|
103
|
+
|
|
104
|
+
Without it a contributor on the supported floor meets a red baseline that says nothing about their
|
|
105
|
+
change. The baseline test passes the same target itself, so it gives one answer everywhere.
|
|
106
|
+
|
|
107
|
+
### Three details that cost time if you meet them the hard way
|
|
108
|
+
|
|
109
|
+
- `ty check` is scoped on purpose. Run over the whole tree it reports hundreds of diagnostics from
|
|
110
|
+
`tests/`, which is full of deliberate negative typing cases: calls that must not type-check.
|
|
111
|
+
- `--all-extras` is not a shortcut for the sync line. Installing `allure` or `behave` makes 100%
|
|
112
|
+
coverage unreachable by construction, because the `except ImportError` branch guarding them stops
|
|
113
|
+
being executed. CI never installs them for the coverage job either.
|
|
114
|
+
- `tests/test_docs_examples.py` is run separately because it executes the snippets in `docs/`, which
|
|
115
|
+
needs the `docs-examples` group and a different collection.
|
|
116
|
+
|
|
117
|
+
### When a gate says an answer moved
|
|
118
|
+
|
|
119
|
+
Two gates record what the library currently decides rather than what it should. Both fail on a change
|
|
120
|
+
and neither is a failure to paper over: read the diff, decide whether the change is meant to ship, then
|
|
121
|
+
re-record it in the same commit so the diff carries the decision into review.
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
ASSERTPY_UPDATE_API=1 uv run pytest tests/test_api_compatibility.py # the public surface
|
|
125
|
+
ASSERTPY_UPDATE_VERDICTS=1 uv run pytest tests/test_verdict_corpus.py # what each input decides
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
The second one's diff is the draft of the release's Behaviour changes section, so read it before
|
|
129
|
+
writing that section rather than after.
|
|
130
|
+
|
|
131
|
+
## Commit style
|
|
132
|
+
|
|
133
|
+
Use [Conventional Commits](https://www.conventionalcommits.org/): `feat:`, `fix:`, `refactor:`, `test:`, `docs:`, `chore:`, etc.
|
|
134
|
+
|
|
135
|
+
## Tests
|
|
136
|
+
|
|
137
|
+
Write tests for every new feature or bug fix. Use `assertpy` assertions in tests.
|
|
138
|
+
|
|
139
|
+
## Documentation examples
|
|
140
|
+
|
|
141
|
+
Guide code blocks are type-checked in CI, and every page that can run in the suite is executed, with what
|
|
142
|
+
each block prints compared with the comment beside it, so an example you add has to run. A block that
|
|
143
|
+
cannot (pseudo-context, a deliberate failure, a rejected counter-example) is marked with an HTML comment
|
|
144
|
+
above the fence. `tests/test_docs_examples.py` lists the markers and what each one exempts, and it and
|
|
145
|
+
`tests/test_docs_typing.py` each name the pages they leave out, with the reason.
|
|
146
|
+
|
|
147
|
+
Setup a page assumes (a domain class, a repository, an HTTP response) goes in
|
|
148
|
+
`tests/docs_fixtures.py`, not into an extra block on the page.
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
Copyright (c) 2015-2019, Activision Publishing, Inc.
|
|
2
|
+
Copyright (c) 2026, Solganis
|
|
2
3
|
All rights reserved.
|
|
3
4
|
|
|
4
5
|
Redistribution and use in source and binary forms, with or without modification,
|
|
@@ -13,7 +14,7 @@ are permitted provided that the following conditions are met:
|
|
|
13
14
|
|
|
14
15
|
3. Neither the name of the copyright holder nor the names of its contributors
|
|
15
16
|
may be used to endorse or promote products derived from this software without
|
|
16
|
-
|
|
17
|
+
specific prior written permission.
|
|
17
18
|
|
|
18
19
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
19
20
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
assertpy-2.35.0/PKG-INFO
ADDED
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: assertpy
|
|
3
|
+
Version: 2.35.0
|
|
4
|
+
Summary: Fluent, fully typed assertion library for Python: your type checker sees only the assertions that fit the value, and every failure carries a structured diff. Soft assertions, polling, inline snapshots, OpenAPI contracts.
|
|
5
|
+
Project-URL: Homepage, https://github.com/assertpy/assertpy
|
|
6
|
+
Project-URL: Repository, https://github.com/assertpy/assertpy
|
|
7
|
+
Project-URL: Issues, https://github.com/assertpy/assertpy/issues
|
|
8
|
+
Author-email: Justin Shacklette <justin@saturnboy.com>
|
|
9
|
+
Maintainer-email: Solganis <solganis.dev@gmail.com>
|
|
10
|
+
License-Expression: BSD-3-Clause
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: assert,assert_that,assertion,assertpy,assertthat,fluent,matchers,pytest,test,testing,type-safe,unittest
|
|
13
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
16
|
+
Classifier: Natural Language :: English
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python
|
|
19
|
+
Classifier: Programming Language :: Python :: 3
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
25
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
26
|
+
Classifier: Topic :: Software Development
|
|
27
|
+
Classifier: Topic :: Software Development :: Testing
|
|
28
|
+
Requires-Python: >=3.10
|
|
29
|
+
Requires-Dist: typing-extensions>=4.0; python_version < '3.11'
|
|
30
|
+
Provides-Extra: allure
|
|
31
|
+
Requires-Dist: allure-pytest>=2.13; extra == 'allure'
|
|
32
|
+
Provides-Extra: behave
|
|
33
|
+
Requires-Dist: behave>=1.3.0; extra == 'behave'
|
|
34
|
+
Provides-Extra: data
|
|
35
|
+
Requires-Dist: numpy>=1.26; extra == 'data'
|
|
36
|
+
Requires-Dist: pandas>=2.0; extra == 'data'
|
|
37
|
+
Requires-Dist: polars>=1.0; extra == 'data'
|
|
38
|
+
Provides-Extra: inline
|
|
39
|
+
Requires-Dist: asttokens>=2.0.4; extra == 'inline'
|
|
40
|
+
Requires-Dist: executing>=2.2; extra == 'inline'
|
|
41
|
+
Provides-Extra: json
|
|
42
|
+
Requires-Dist: idna>=3.7; extra == 'json'
|
|
43
|
+
Requires-Dist: jsonpath-ng>=1.8; extra == 'json'
|
|
44
|
+
Requires-Dist: jsonschema>=4.18; extra == 'json'
|
|
45
|
+
Requires-Dist: referencing>=0.28.4; extra == 'json'
|
|
46
|
+
Provides-Extra: numpy
|
|
47
|
+
Requires-Dist: numpy>=1.26; extra == 'numpy'
|
|
48
|
+
Provides-Extra: pandas
|
|
49
|
+
Requires-Dist: pandas>=2.0; extra == 'pandas'
|
|
50
|
+
Provides-Extra: polars
|
|
51
|
+
Requires-Dist: polars>=1.0; extra == 'polars'
|
|
52
|
+
Description-Content-Type: text/markdown
|
|
53
|
+
|
|
54
|
+
<p align="center">
|
|
55
|
+
<picture>
|
|
56
|
+
<source media="(prefers-color-scheme: dark)" srcset="docs/logo-dark.svg">
|
|
57
|
+
<source media="(prefers-color-scheme: light)" srcset="docs/logo.svg">
|
|
58
|
+
<img src="docs/logo.svg" alt="assertpy" width="280">
|
|
59
|
+
</picture>
|
|
60
|
+
<br>
|
|
61
|
+
<b>Fully typed fluent assertion library for Python</b>
|
|
62
|
+
</p>
|
|
63
|
+
|
|
64
|
+
<p align="center">
|
|
65
|
+
<a href="https://github.com/assertpy/assertpy/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/assertpy/assertpy/ci.yml?branch=main&label=CI" alt="CI"></a>
|
|
66
|
+
<a href="https://codecov.io/gh/assertpy/assertpy"><img src="https://codecov.io/gh/assertpy/assertpy/graph/badge.svg" alt="Coverage"></a>
|
|
67
|
+
<a href="https://pypi.org/project/assertpy/"><img src="https://img.shields.io/pypi/v/assertpy" alt="PyPI version"></a>
|
|
68
|
+
<a href="https://pypi.org/project/assertpy/"><img src="https://img.shields.io/pypi/pyversions/assertpy" alt="Python"></a>
|
|
69
|
+
<a href="https://pepy.tech/projects/assertpy"><img src="https://static.pepy.tech/badge/assertpy/month" alt="Downloads"></a>
|
|
70
|
+
<br>
|
|
71
|
+
<a href="https://assertpy.github.io/assertpy/concepts/type-safety/"><img src="https://img.shields.io/badge/overloads-ty%20%7C%20mypy%20%7C%20pyright%20%7C%20pyrefly-2ea043" alt="every public overload checked by ty, mypy --strict, pyright and pyrefly with zero suppressions"></a>
|
|
72
|
+
<a href="https://assertpy.github.io/assertpy/"><img src="https://img.shields.io/badge/docs-online-black" alt="Documentation"></a>
|
|
73
|
+
<a href="https://scorecard.dev/viewer/?uri=github.com/assertpy/assertpy"><img src="https://img.shields.io/ossf-scorecard/github.com/assertpy/assertpy?label=OpenSSF%20Scorecard" alt="OpenSSF Scorecard"></a>
|
|
74
|
+
</p>
|
|
75
|
+
|
|
76
|
+
<h2 align="center"><a href="https://assertpy.github.io/assertpy/getting-started/quickstart/">Quick start</a></h2>
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
pip install assertpy
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
from assertpy import assert_that
|
|
84
|
+
|
|
85
|
+
def test_user():
|
|
86
|
+
user = {"name": "Alice", "age": 30, "roles": ["viewer", "editor"]}
|
|
87
|
+
|
|
88
|
+
assert_that(user).contains_key("name", "age")
|
|
89
|
+
assert_that(user).contains_entry({"name": "Alice"})
|
|
90
|
+
assert_that(user["age"]).is_between(18, 120)
|
|
91
|
+
assert_that(user["roles"]).contains("viewer").does_not_contain("admin")
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
The [full documentation](https://assertpy.github.io/assertpy/) covers every assertion, matcher, and integration.
|
|
95
|
+
|
|
96
|
+
<table>
|
|
97
|
+
<tr>
|
|
98
|
+
<td valign="top" width="50%">
|
|
99
|
+
<a href="#why-fluent-assertions"><b>Failures that point at the difference</b></a><br>
|
|
100
|
+
A recursive diff names the exact path that differs, in color, instead of dumping both structures
|
|
101
|
+
</td>
|
|
102
|
+
<td valign="top" width="50%">
|
|
103
|
+
<a href="#type-aware-autocomplete"><b>Type-aware autocomplete</b></a><br>
|
|
104
|
+
<code>assert_that()</code> returns a protocol per value type, so your IDE offers the methods that fit
|
|
105
|
+
</td>
|
|
106
|
+
</tr>
|
|
107
|
+
<tr>
|
|
108
|
+
<td valign="top">
|
|
109
|
+
<a href="#typed-narrowing"><b>Typed narrowing</b></a><br>
|
|
110
|
+
An assertion hands the value back statically narrowed, with no <code>cast</code> and no bare <code>assert</code>
|
|
111
|
+
</td>
|
|
112
|
+
<td valign="top">
|
|
113
|
+
<a href="#features"><b>Composable matchers</b></a><br>
|
|
114
|
+
45 matchers that combine with <code>&</code>, <code>|</code>, <code>~</code> and nest inside the expected structure itself
|
|
115
|
+
</td>
|
|
116
|
+
</tr>
|
|
117
|
+
<tr>
|
|
118
|
+
<td valign="top">
|
|
119
|
+
<a href="#features"><b>Built for test suites</b></a><br>
|
|
120
|
+
Soft assertions, polling for eventual consistency, snapshots, and expected-exception chains
|
|
121
|
+
</td>
|
|
122
|
+
<td valign="top">
|
|
123
|
+
<a href="#integrations"><b>Integrations</b></a><br>
|
|
124
|
+
Allure, Behave, JSON Path and Schema, pandas, polars, numpy, and OpenAPI response contracts
|
|
125
|
+
</td>
|
|
126
|
+
</tr>
|
|
127
|
+
</table>
|
|
128
|
+
|
|
129
|
+
<h2 align="center"><a href="https://assertpy.github.io/assertpy/getting-started/comparison/">Why fluent assertions?</a></h2>
|
|
130
|
+
|
|
131
|
+
`assert` states a condition well. pytest reports it well.
|
|
132
|
+
|
|
133
|
+
What it cannot say is *where* two structures differ. It prints both and leaves the reading to you:
|
|
134
|
+
|
|
135
|
+
```text
|
|
136
|
+
assert response == expected
|
|
137
|
+
E AssertionError: assert {'id': 1, ...} == {'id': 1, ...}
|
|
138
|
+
E Omitting 1 identical items, use -vv to show
|
|
139
|
+
E Differing items:
|
|
140
|
+
E {'user': {'name': 'Alice', 'role': 'superadmin'}} != {'user': {'name': 'Alice', 'role': 'admin'}}
|
|
141
|
+
E {'status': 'active'} != {'status': 'disabled'}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
assertpy names the [exact path](https://assertpy.github.io/assertpy/guides/errors/#rich-pytest-diffs), in color:
|
|
145
|
+
|
|
146
|
+
<!-- docs-guard: skip -->
|
|
147
|
+
```python
|
|
148
|
+
assert_that(response).is_equal_to(expected)
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
<p align="center">
|
|
152
|
+
<img src="https://raw.githubusercontent.com/assertpy/assertpy/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">
|
|
153
|
+
</p>
|
|
154
|
+
|
|
155
|
+
It recurses through nested containers. Matcher predicates get the same treatment.
|
|
156
|
+
|
|
157
|
+
For dynamic fields like IDs, assert a subset with
|
|
158
|
+
[`matches_structure()`](https://assertpy.github.io/assertpy/guides/matchers/#structural-matching).
|
|
159
|
+
|
|
160
|
+
The chain is the other half. Your IDE offers only the
|
|
161
|
+
[methods that fit the value](https://assertpy.github.io/assertpy/concepts/type-safety/).
|
|
162
|
+
|
|
163
|
+
<!-- docs-guard: skip -->
|
|
164
|
+
```python
|
|
165
|
+
assert_that(items).is_instance_of(list).is_length(3).contains("admin")
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Matchers are ordinary values that answer `==`, the way `unittest.mock.ANY` does.
|
|
169
|
+
|
|
170
|
+
Nothing is patched, so a matcher can sit inside the expected structure at any depth:
|
|
171
|
+
|
|
172
|
+
```python
|
|
173
|
+
response = {"id": 7, "user": {"name": "Alice", "age": 30}, "tags": ["a", "b"]}
|
|
174
|
+
|
|
175
|
+
assert_that(response).is_equal_to(
|
|
176
|
+
{
|
|
177
|
+
"id": match.greater_than(0),
|
|
178
|
+
"user": {"name": "Alice", "age": match.between(18, 120)},
|
|
179
|
+
"tags": ["a", "b"],
|
|
180
|
+
}
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
# or keep the bare `assert`, and pytest's own rewriting reports it
|
|
184
|
+
assert response == {
|
|
185
|
+
"id": match.greater_than(0),
|
|
186
|
+
"user": match.ignore(),
|
|
187
|
+
"tags": ["a", "b"],
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
The fluent form keeps the path-level diff, the bare form keeps pytest's.
|
|
192
|
+
|
|
193
|
+
There are [45 matchers](https://assertpy.github.io/assertpy/guides/matchers/), combining with `&`, `|` and `~`.
|
|
194
|
+
|
|
195
|
+
<p align="center">
|
|
196
|
+
<img src="https://raw.githubusercontent.com/assertpy/assertpy/main/docs/assets/diff-gallery.png" width="640" alt="Structured diffs in the terminal: dict path, list element, set extra/missing, and structural-matcher predicate diffs, side by side">
|
|
197
|
+
</p>
|
|
198
|
+
|
|
199
|
+
<h2 align="center"><a href="https://assertpy.github.io/assertpy/concepts/type-safety/">Type-aware autocomplete</a></h2>
|
|
200
|
+
|
|
201
|
+
`assert_that()` uses `@overload` to return type-specific Protocols.<br>
|
|
202
|
+
Your IDE shows only methods relevant to the value you're testing, not the whole surface:
|
|
203
|
+
|
|
204
|
+
- `assert_that("hello").` → string methods: `starts_with`, `matches`, `is_alpha`, ...
|
|
205
|
+
- `assert_that(42).` → numeric methods: `is_positive`, `is_between`, `is_close_to`, ...
|
|
206
|
+
- `assert_that(Path("/tmp")).` → path methods: `exists`, `is_file`, `is_readable`, ...
|
|
207
|
+
- `assert_that(my_dict).` → dict methods: `contains_key`, `contains_entry`, `has_json_path`, ...
|
|
208
|
+
- `assert_that(b"\x89PNG").` → bytes methods: `starts_with_bytes`, `is_valid_utf8`, `decoded_as`, ...
|
|
209
|
+
|
|
210
|
+
15 type-specific Protocols instead of one `Any`<br>
|
|
211
|
+
Works in PyCharm, VS Code, and any editor that runs a type checker
|
|
212
|
+
|
|
213
|
+
<h2 align="center"><a href="https://assertpy.github.io/assertpy/concepts/type-safety/#typed-narrowing-with-value">Typed narrowing</a></h2>
|
|
214
|
+
|
|
215
|
+
An assertion hands the value back, statically narrowed.
|
|
216
|
+
|
|
217
|
+
`is_not_none()` strips `None`, `is_instance_of()` narrows to the class, and `.value` returns it:
|
|
218
|
+
|
|
219
|
+
<!-- docs-guard: skip -->
|
|
220
|
+
```python
|
|
221
|
+
order = assert_that(repo.find(42)).is_not_none().is_instance_of(PaidOrder).value
|
|
222
|
+
order.refund() # statically PaidOrder - verified by ty, mypy, pyright and pyrefly
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
For API tests,
|
|
226
|
+
[`assert_conforms()`](https://assertpy.github.io/assertpy/concepts/type-safety/#contract-narrowing-with-assert_conforms)
|
|
227
|
+
validates a payload against a Pydantic model and narrows to it. `exact=True` catches contract drift:
|
|
228
|
+
|
|
229
|
+
<!-- docs-guard: skip -->
|
|
230
|
+
```python
|
|
231
|
+
data = assert_conforms(response.json(), OrderModel).value # data: OrderModel
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
<h2 align="center"><a href="https://assertpy.github.io/assertpy/guides/errors/#asking-instead-of-asserting">A failure you can read from code</a></h2>
|
|
235
|
+
|
|
236
|
+
An exception is the right default. Reading the result means catching it.
|
|
237
|
+
|
|
238
|
+
`check()` runs the next assertion for its verdict instead:
|
|
239
|
+
|
|
240
|
+
```python
|
|
241
|
+
response = {"user": {"name": "Alice", "role": "superadmin"}, "status": "active"}
|
|
242
|
+
expected = {"user": {"name": "Alice", "role": "admin"}, "status": "active"}
|
|
243
|
+
|
|
244
|
+
outcome = assert_that(response).check().is_equal_to(expected)
|
|
245
|
+
|
|
246
|
+
if not outcome and outcome.diff:
|
|
247
|
+
print(outcome.diff.entries[0].path) # user.role
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
It is truthy when the assertion held. When it did not, it carries `.message`, `.actual`, `.expected`
|
|
251
|
+
and a walkable `.diff`. So does `AssertionFailure`.
|
|
252
|
+
|
|
253
|
+
So a reporter reads structure instead of parsing a string. That is how the
|
|
254
|
+
[Allure integration](https://assertpy.github.io/assertpy/extending/integrations/#allure) works.
|
|
255
|
+
Anything else you build can too.
|
|
256
|
+
|
|
257
|
+
<h2 align="center">Features</h2>
|
|
258
|
+
|
|
259
|
+
**Fluent API**
|
|
260
|
+
|
|
261
|
+
- [**Structural matching**](https://assertpy.github.io/assertpy/guides/matchers/#structural-matching): `matches_structure()` for declarative dict/API-response validation
|
|
262
|
+
- [**Recursive field assertions**](https://assertpy.github.io/assertpy/guides/assertions/#recursive-field-assertions): `all_fields_satisfy()` / `has_no_none_fields()` apply a predicate to every leaf of an object graph
|
|
263
|
+
- [**Universal negation**](https://assertpy.github.io/assertpy/guides/fluent/#universal-negation): `.not_` inverts any assertion, no dedicated `is_not_*` methods
|
|
264
|
+
- [**Collection pipeline**](https://assertpy.github.io/assertpy/guides/fluent/#collection-pipeline): `filtered_on()`, `mapped()`, `flat_mapped()`, `first()`, `last()`, `element()`, `single()`
|
|
265
|
+
- [**Positional & pairwise checks**](https://assertpy.github.io/assertpy/guides/assertions/#lists): `satisfies_exactly()`, `zip_satisfies()`, `contains_only_once()`, `has_same_size_as()`, plus `*_in_any_order` variants
|
|
266
|
+
|
|
267
|
+
**Type safety**
|
|
268
|
+
|
|
269
|
+
- [**Refinement predicates**](https://assertpy.github.io/assertpy/concepts/type-safety/#refinement-narrowing-with-a-typeis-predicate-advanced): `satisfies()` takes a `TypeIs` predicate, so a domain check narrows the chain too
|
|
270
|
+
- [**Contract testing**](https://assertpy.github.io/assertpy/concepts/type-safety/#contract-narrowing-with-assert_conforms): `assert_conforms()` validates a raw payload against a Pydantic model and narrows to it. [`exact=True`](https://assertpy.github.io/assertpy/concepts/type-safety/#contract-drift-with-exacttrue) catches contract drift, `each=True` validates list endpoints
|
|
271
|
+
|
|
272
|
+
**Built-in types**
|
|
273
|
+
|
|
274
|
+
- [Strings](https://assertpy.github.io/assertpy/guides/assertions/#strings), [numbers](https://assertpy.github.io/assertpy/guides/assertions/#numbers), [lists](https://assertpy.github.io/assertpy/guides/assertions/#lists), [tuples](https://assertpy.github.io/assertpy/guides/assertions/#tuples), [sets](https://assertpy.github.io/assertpy/guides/assertions/#sets), [dicts](https://assertpy.github.io/assertpy/guides/assertions/#dicts), [dates](https://assertpy.github.io/assertpy/guides/assertions/#dates), [booleans](https://assertpy.github.io/assertpy/guides/assertions/#booleans), [objects](https://assertpy.github.io/assertpy/guides/assertions/#objects), [bytes](https://assertpy.github.io/assertpy/guides/assertions/#bytes--bytearray), [files](https://assertpy.github.io/assertpy/guides/assertions/#files), [exceptions](https://assertpy.github.io/assertpy/guides/errors/#expected-exceptions)
|
|
275
|
+
- [**Bytes assertions**](https://assertpy.github.io/assertpy/guides/assertions/#bytes--bytearray): `is_valid_utf8()`, `starts_with_bytes()`, `is_hex_equal_to()`, `decoded_as()` for `bytes`/`bytearray`
|
|
276
|
+
- [**Dynamic assertions**](https://assertpy.github.io/assertpy/guides/assertions/#dynamic-assertions-on-objects): `has_<name>()` for any attribute, property, or zero-argument method
|
|
277
|
+
- [**Dict comparison**](https://assertpy.github.io/assertpy/guides/assertions/#selective-comparison-ignore--include): `is_equal_to(ignore=..., include=...)` for selective key/field matching by name, regex, or type
|
|
278
|
+
- [**Recursive comparison**](https://assertpy.github.io/assertpy/guides/assertions/#recursive-comparison-tolerance--custom-comparators): `is_equal_to()` with `tolerance`, `comparators`, or `ignore_null` for nested structures
|
|
279
|
+
- [**Extracting**](https://assertpy.github.io/assertpy/guides/assertions/#extracting-attributes-from-objects): flatten collections on attributes with `filter` and `sort` support
|
|
280
|
+
- [**Regex group extraction**](https://assertpy.github.io/assertpy/guides/data/#regex-group-extraction): `extracting_group()` and `matches_with_groups()` for regex captures
|
|
281
|
+
|
|
282
|
+
**Testing**
|
|
283
|
+
|
|
284
|
+
- [**Soft assertions**](https://assertpy.github.io/assertpy/guides/testing/#soft-assertions): thread-safe and async-safe via `contextvars`, each failure reported with its `file:line`. Group with `sa.group()` or `assert_all()`
|
|
285
|
+
- [**Polling assertions**](https://assertpy.github.io/assertpy/guides/testing/#async-assertions): `eventually()` (async) / `eventually_sync()` (blocking) retry for eventual consistency, with a convergence trace on timeout
|
|
286
|
+
- [**Expected exceptions**](https://assertpy.github.io/assertpy/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()`)
|
|
287
|
+
- [**HTTP responses**](https://assertpy.github.io/assertpy/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
|
|
288
|
+
- [**Snapshot testing**](https://assertpy.github.io/assertpy/guides/testing/#snapshot-testing): an external JSON file, an [inline](https://assertpy.github.io/assertpy/guides/testing/#inline-snapshots) value recorded into the test source, or a [value-tolerant contract](https://assertpy.github.io/assertpy/guides/testing/#contract-snapshots), all updated with `--assertpy-snapshot-update`
|
|
289
|
+
- [**OpenAPI response contracts**](https://assertpy.github.io/assertpy/reference/json/#assertpy.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
|
|
290
|
+
|
|
291
|
+
**Failure reporting**
|
|
292
|
+
|
|
293
|
+
- [**Structured errors**](https://assertpy.github.io/assertpy/guides/errors/#structured-errors): `AssertionFailure` carries `.actual`, `.expected` and `.diff`, and the diff renders into the message, so it shows off pytest too
|
|
294
|
+
- [**Assertions as values**](https://assertpy.github.io/assertpy/guides/errors/#asking-instead-of-asserting): `check()` runs the next assertion for its verdict instead of raising, handing back an `AssertionOutcome`
|
|
295
|
+
- [**Rich pytest diffs**](https://assertpy.github.io/assertpy/guides/errors/#rich-pytest-diffs): recursive diffs across containers, dataclasses, attrs and Pydantic models, with intra-line carets for strings
|
|
296
|
+
- [**Failures that say why**](https://assertpy.github.io/assertpy/guides/errors/#some-failures-say-why-not-only-what): where one fact explains a failure, a line under the message says it. An id the payload holds as text (`7` against `"7"`), a NaN, two instances compared by identity, surrounding whitespace. A failed `contains()` or `is_in()` names the closest row, and forty rows that differ in one field are named as one place
|
|
297
|
+
|
|
298
|
+
**Plugin for pytest**
|
|
299
|
+
|
|
300
|
+
Set from the command line or from `[tool.pytest.ini_options]`, not from a call
|
|
301
|
+
|
|
302
|
+
- [**Failure clustering**](https://assertpy.github.io/assertpy/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, `assertpy_failure_clusters = "off"` turns it off
|
|
303
|
+
- [**IDE comparison window**](https://assertpy.github.io/assertpy/guides/errors/#the-comparison-window-of-an-ide): a failed `is_equal_to()` or `has_<name>()` gets PyCharm's "Click to see difference" link, as a plain `assert` does, and the window shows the two values it compared. Under `ignore=` or `include=` those go without the keys left out. The plugin hands them to `pytest_assertrepr_compare`, the hook the IDE's runner listens to. On by default, `assertpy_comparison = "off"` turns it off
|
|
304
|
+
- [**Diagnostic profiles**](https://assertpy.github.io/assertpy/guides/errors/#configuration): `assertpy_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://assertpy.github.io/assertpy/guides/assertions/#assertions-that-checked-nothing): `--assertpy-vacuous` warns when a universal assertion passes over an empty collection, having checked nothing
|
|
306
|
+
- [**Dangling-assertion detector**](https://assertpy.github.io/assertpy/guides/assertions/#assertions-that-never-ran): `--assertpy-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
|
|
307
|
+
|
|
308
|
+
**Extensibility**
|
|
309
|
+
|
|
310
|
+
- [**Custom matchers**](https://assertpy.github.io/assertpy/guides/matchers/#custom-matchers): `register_matcher()` composes existing ones, `BaseMatcher` carries its own predicate. Both compose with `&`, `|`, `~`
|
|
311
|
+
- [**Custom assertions**](https://assertpy.github.io/assertpy/extending/custom-assertions/): `add_extension()` adds a method to the builder
|
|
312
|
+
|
|
313
|
+
<h2 align="center"><a href="https://assertpy.github.io/assertpy/extending/integrations/">Integrations</a></h2>
|
|
314
|
+
|
|
315
|
+
- [**Allure**](https://assertpy.github.io/assertpy/extending/integrations/#allure) (`pip install assertpy[allure]`): the pytest plugin auto-attaches structured diff and actual/expected data to Allure reports, in three configurable modes
|
|
316
|
+
- [**Behave**](https://assertpy.github.io/assertpy/extending/integrations/#behave) (`pip install assertpy[behave]`): ready-made parameter types (`PositiveInt`, `NonEmptyString`, ...) for step definitions like `{age:PositiveInt}`
|
|
317
|
+
- [**JSON**](https://assertpy.github.io/assertpy/guides/data/#json-path--schema) (`pip install assertpy[json]`): JSONPath navigation (`at_json_path()`, `has_json_path()`) and JSON Schema validation (`matches_json_schema()`)
|
|
318
|
+
- [**Data frames**](https://assertpy.github.io/assertpy/extending/integrations/#data-frames-and-arrays) (`pip install assertpy[pandas]` / `[polars]` / `[numpy]`): fluent equality for pandas/polars frames and numpy arrays, carrying each library's own diff
|
|
319
|
+
|
|
320
|
+
---
|
|
321
|
+
|
|
322
|
+
<p align="center">
|
|
323
|
+
<a href="https://github.com/assertpy/assertpy/blob/main/LICENSE">BSD 3-Clause License</a>
|
|
324
|
+
</p>
|