Doing dirty (but extremely useful) things with equals.

dirty-equals

Doing dirty (but extremely useful) things with equals.

CI Coverage pypi versions license


Documentation: dirty-equals.helpmanual.io

Source Code: github.com/samuelcolvin/dirty-equals


dirty-equals is a python library that (mis)uses the __eq__ method to make python code (generally unit tests) more declarative and therefore easier to read and write.

dirty-equals can be used in whatever context you like, but it comes into its own when writing unit tests for applications where you're commonly checking the response to API calls and the contents of a database.

Usage

Here's a trivial example of what dirty-equals can do:

from dirty_equals import IsPositive

assert 1 == IsPositive
assert -2 == IsPositive  # this will fail!

That doesn't look very useful yet!, but consider the following unit test code using dirty-equals:

from dirty_equals import IsJson, IsNow, IsPositiveInt, IsStr

...

# user_data is a dict returned from a database or API which we want to test
assert user_data == {
    # we want to check that id is a positive int
    'id': IsPositiveInt,
    # we know avatar_file should be a string, but we need a regex as we don't know whole value
    'avatar_file': IsStr(regex=r'/[a-z0-9\-]{10}/example\.png'),
    # settings_json is JSON, but it's more robust to compare the value it encodes, not strings
    'settings_json': IsJson({'theme': 'dark', 'language': 'en'}),
    # created_ts is datetime, we don't know the exact value, but we know it should be close to now
    'created_ts': IsNow(delta=3),
}

Without dirty-equals, you'd have to compare individual fields and/or modify some fields before comparison - the test would not be declarative or as clear.

dirty-equals can do so much more than that, for example:

  • IsPartialDict lets you compare a subset of a dictionary
  • IsStrictDict lets you confirm order in a dictionary
  • IsList and IsTuple lets you compare partial lists and tuples, with or without order constraints
  • nesting any of these types inside any others
  • IsInstance lets you simply confirm the type of an object
  • You can even use boolean operators | and & to combine multiple conditions
  • and much more...

Installation

Simply:

pip install dirty-equals

dirty-equals requires Python 3.7+.

Owner
Samuel Colvin
Python & Rust developer with a hint of TypeScript, maintainer of pydantic and other libraries. he/him.
Samuel Colvin
Comments
  • Numerous test failures with pypy3.9

    Numerous test failures with pypy3.9

    The following tests fail on pypy3.9 (7.3.9):

    FAILED tests/test_base.py::test_not_repr - Failed: DID NOT RAISE <class 'AssertionError'>
    FAILED tests/test_boolean.py::test_dirty_not_equals - Failed: DID NOT RAISE <class 'AssertionError'>
    FAILED tests/test_dict.py::test_is_dict[input_value16-expected16] - AssertionError: assert {'a': 1, 'b': None} == IsIgnoreDict(a=1)
    FAILED tests/test_dict.py::test_is_dict[input_value17-expected17] - assert {1: 10, 2: None} == IsIgnoreDict(1=10)
    FAILED tests/test_dict.py::test_is_dict[input_value20-expected20] - assert {1: 10, 2: False} == IsIgnoreDict[ignore={False}](1=10)
    FAILED tests/test_dict.py::test_callable_ignore - AssertionError: assert {'a': 1, 'b': 42} == IsDict[ignore=ignore_42](a=1)
    FAILED tests/test_dict.py::test_ignore - AssertionError: assert {'a': 1, 'b': 2, 'c': 3, 'd': 4} == IsDict[ignore=custom_ignore](a=1...
    FAILED tests/test_dict.py::test_ignore_with_is_str - AssertionError: assert {'dob': None, 'id': 123, 'street_address': None, 'token'...
    FAILED tests/test_dict.py::test_unhashable_value - AssertionError: assert {'b': {'a': 1}, 'c': None} == IsIgnoreDict(b={'a': 1})
    FAILED tests/test_docs.py::test_docs_examples[dirty_equals/_inspection.py:172-189] - AssertionError: assert <_inspection_172_189.Foo...
    FAILED tests/test_docs.py::test_docs_examples[dirty_equals/_dict.py:186-204] - AssertionError: assert {'a': 1, 'b': 2, 'c': None} ==...
    FAILED tests/test_inspection.py::test_has_attributes[-HasAttributes(a=IsInt, b=IsStr)] - assert <tests.test_inspection.Foo object at...
    

    Full output:

    ========================================================= test session starts =========================================================
    platform linux -- Python 3.9.12[pypy-7.3.9-final], pytest-7.1.2, pluggy-1.0.0
    rootdir: /tmp/dirty-equals, configfile: pyproject.toml, testpaths: tests
    plugins: forked-1.4.0, xdist-2.5.0, xprocess-0.18.1, anyio-3.5.0
    collected 484 items                                                                                                                   
    
    tests/test_base.py ......F....................                                                                                  [  5%]
    tests/test_boolean.py ..........................F................                                                               [ 14%]
    tests/test_datetime.py .................................................                                                        [ 24%]
    tests/test_dict.py ................FF..F................F.................FFF                                                   [ 36%]
    tests/test_docs.py ..........................F...F..................                                                            [ 46%]
    tests/test_inspection.py ................F...........                                                                           [ 52%]
    tests/test_list_tuple.py ..............................................................................                         [ 68%]
    tests/test_numeric.py ..........................................................                                                [ 80%]
    tests/test_other.py ...................................                                                                         [ 87%]
    tests/test_strings.py ...........................................................                                               [100%]
    
    ============================================================== FAILURES ===============================================================
    ____________________________________________________________ test_not_repr ____________________________________________________________
    
        def test_not_repr():
            v = ~IsInt
            assert str(v) == '~IsInt'
        
            with pytest.raises(AssertionError):
    >           assert 1 == v
    E           Failed: DID NOT RAISE <class 'AssertionError'>
    
    tests/test_base.py:66: Failed
    ________________________________________________________ test_dirty_not_equals ________________________________________________________
    
        def test_dirty_not_equals():
            with pytest.raises(AssertionError):
    >           assert 0 != IsFalseLike
    E           Failed: DID NOT RAISE <class 'AssertionError'>
    
    tests/test_boolean.py:48: Failed
    _______________________________________________ test_is_dict[input_value16-expected16] ________________________________________________
    
    input_value = {'a': 1, 'b': None}, expected = IsIgnoreDict(a=1)
    
        @pytest.mark.parametrize(
            'input_value,expected',
            [
                ({}, IsDict),
                ({}, IsDict()),
                ({'a': 1}, IsDict(a=1)),
                ({1: 2}, IsDict({1: 2})),
                ({'a': 1, 'b': 2}, IsDict(a=1, b=2)),
                ({'b': 2, 'a': 1}, IsDict(a=1, b=2)),
                ({'a': 1, 'b': None}, IsDict(a=1, b=None)),
                ({'a': 1, 'b': 3}, ~IsDict(a=1, b=2)),
                # partial dict
                ({1: 10, 2: 20}, IsPartialDict({1: 10})),
                ({1: 10}, IsPartialDict({1: 10})),
                ({1: 10, 2: 20}, IsPartialDict({1: 10})),
                ({1: 10, 2: 20}, IsDict({1: 10}).settings(partial=True)),
                ({1: 10}, ~IsPartialDict({1: 10, 2: 20})),
                ({1: 10, 2: None}, ~IsPartialDict({1: 10, 2: 20})),
                # ignore dict
                ({}, IsIgnoreDict()),
                ({'a': 1, 'b': 2}, IsIgnoreDict(a=1, b=2)),
                ({'a': 1, 'b': None}, IsIgnoreDict(a=1)),
                ({1: 10, 2: None}, IsIgnoreDict({1: 10})),
                ({'a': 1, 'b': 2}, ~IsIgnoreDict(a=1)),
                ({1: 10, 2: False}, ~IsIgnoreDict({1: 10})),
                ({1: 10, 2: False}, IsIgnoreDict({1: 10}).settings(ignore={False})),
                # strict dict
                ({}, IsStrictDict()),
                ({'a': 1, 'b': 2}, IsStrictDict(a=1, b=2)),
                ({'a': 1, 'b': 2}, ~IsStrictDict(b=2, a=1)),
                ({1: 10, 2: 20}, IsStrictDict({1: 10, 2: 20})),
                ({1: 10, 2: 20}, ~IsStrictDict({2: 20, 1: 10})),
                ({1: 10, 2: 20}, ~IsDict({2: 20, 1: 10}).settings(strict=True)),
                # combining types
                ({'a': 1, 'b': 2, 'c': 3}, IsStrictDict(a=1, c=3).settings(partial=True)),
                ({'a': 1, 'b': 2, 'c': 3}, IsStrictDict(a=1, b=2).settings(partial=True)),
                ({'a': 1, 'b': 2, 'c': 3}, IsStrictDict(b=2, c=3).settings(partial=True)),
                ({'a': 1, 'c': 3, 'b': 2}, ~IsStrictDict(b=2, c=3).settings(partial=True)),
            ],
        )
        def test_is_dict(input_value, expected):
    >       assert input_value == expected
    E       AssertionError: assert {'a': 1, 'b': None} == IsIgnoreDict(a=1)
    
    tests/test_dict.py:47: AssertionError
    _______________________________________________ test_is_dict[input_value17-expected17] ________________________________________________
    
    input_value = {1: 10, 2: None}, expected = IsIgnoreDict(1=10)
    
        @pytest.mark.parametrize(
            'input_value,expected',
            [
                ({}, IsDict),
                ({}, IsDict()),
                ({'a': 1}, IsDict(a=1)),
                ({1: 2}, IsDict({1: 2})),
                ({'a': 1, 'b': 2}, IsDict(a=1, b=2)),
                ({'b': 2, 'a': 1}, IsDict(a=1, b=2)),
                ({'a': 1, 'b': None}, IsDict(a=1, b=None)),
                ({'a': 1, 'b': 3}, ~IsDict(a=1, b=2)),
                # partial dict
                ({1: 10, 2: 20}, IsPartialDict({1: 10})),
                ({1: 10}, IsPartialDict({1: 10})),
                ({1: 10, 2: 20}, IsPartialDict({1: 10})),
                ({1: 10, 2: 20}, IsDict({1: 10}).settings(partial=True)),
                ({1: 10}, ~IsPartialDict({1: 10, 2: 20})),
                ({1: 10, 2: None}, ~IsPartialDict({1: 10, 2: 20})),
                # ignore dict
                ({}, IsIgnoreDict()),
                ({'a': 1, 'b': 2}, IsIgnoreDict(a=1, b=2)),
                ({'a': 1, 'b': None}, IsIgnoreDict(a=1)),
                ({1: 10, 2: None}, IsIgnoreDict({1: 10})),
                ({'a': 1, 'b': 2}, ~IsIgnoreDict(a=1)),
                ({1: 10, 2: False}, ~IsIgnoreDict({1: 10})),
                ({1: 10, 2: False}, IsIgnoreDict({1: 10}).settings(ignore={False})),
                # strict dict
                ({}, IsStrictDict()),
                ({'a': 1, 'b': 2}, IsStrictDict(a=1, b=2)),
                ({'a': 1, 'b': 2}, ~IsStrictDict(b=2, a=1)),
                ({1: 10, 2: 20}, IsStrictDict({1: 10, 2: 20})),
                ({1: 10, 2: 20}, ~IsStrictDict({2: 20, 1: 10})),
                ({1: 10, 2: 20}, ~IsDict({2: 20, 1: 10}).settings(strict=True)),
                # combining types
                ({'a': 1, 'b': 2, 'c': 3}, IsStrictDict(a=1, c=3).settings(partial=True)),
                ({'a': 1, 'b': 2, 'c': 3}, IsStrictDict(a=1, b=2).settings(partial=True)),
                ({'a': 1, 'b': 2, 'c': 3}, IsStrictDict(b=2, c=3).settings(partial=True)),
                ({'a': 1, 'c': 3, 'b': 2}, ~IsStrictDict(b=2, c=3).settings(partial=True)),
            ],
        )
        def test_is_dict(input_value, expected):
    >       assert input_value == expected
    E       assert {1: 10, 2: None} == IsIgnoreDict(1=10)
    
    tests/test_dict.py:47: AssertionError
    _______________________________________________ test_is_dict[input_value20-expected20] ________________________________________________
    
    input_value = {1: 10, 2: False}, expected = IsIgnoreDict[ignore={False}](1=10)
    
        @pytest.mark.parametrize(
            'input_value,expected',
            [
                ({}, IsDict),
                ({}, IsDict()),
                ({'a': 1}, IsDict(a=1)),
                ({1: 2}, IsDict({1: 2})),
                ({'a': 1, 'b': 2}, IsDict(a=1, b=2)),
                ({'b': 2, 'a': 1}, IsDict(a=1, b=2)),
                ({'a': 1, 'b': None}, IsDict(a=1, b=None)),
                ({'a': 1, 'b': 3}, ~IsDict(a=1, b=2)),
                # partial dict
                ({1: 10, 2: 20}, IsPartialDict({1: 10})),
                ({1: 10}, IsPartialDict({1: 10})),
                ({1: 10, 2: 20}, IsPartialDict({1: 10})),
                ({1: 10, 2: 20}, IsDict({1: 10}).settings(partial=True)),
                ({1: 10}, ~IsPartialDict({1: 10, 2: 20})),
                ({1: 10, 2: None}, ~IsPartialDict({1: 10, 2: 20})),
                # ignore dict
                ({}, IsIgnoreDict()),
                ({'a': 1, 'b': 2}, IsIgnoreDict(a=1, b=2)),
                ({'a': 1, 'b': None}, IsIgnoreDict(a=1)),
                ({1: 10, 2: None}, IsIgnoreDict({1: 10})),
                ({'a': 1, 'b': 2}, ~IsIgnoreDict(a=1)),
                ({1: 10, 2: False}, ~IsIgnoreDict({1: 10})),
                ({1: 10, 2: False}, IsIgnoreDict({1: 10}).settings(ignore={False})),
                # strict dict
                ({}, IsStrictDict()),
                ({'a': 1, 'b': 2}, IsStrictDict(a=1, b=2)),
                ({'a': 1, 'b': 2}, ~IsStrictDict(b=2, a=1)),
                ({1: 10, 2: 20}, IsStrictDict({1: 10, 2: 20})),
                ({1: 10, 2: 20}, ~IsStrictDict({2: 20, 1: 10})),
                ({1: 10, 2: 20}, ~IsDict({2: 20, 1: 10}).settings(strict=True)),
                # combining types
                ({'a': 1, 'b': 2, 'c': 3}, IsStrictDict(a=1, c=3).settings(partial=True)),
                ({'a': 1, 'b': 2, 'c': 3}, IsStrictDict(a=1, b=2).settings(partial=True)),
                ({'a': 1, 'b': 2, 'c': 3}, IsStrictDict(b=2, c=3).settings(partial=True)),
                ({'a': 1, 'c': 3, 'b': 2}, ~IsStrictDict(b=2, c=3).settings(partial=True)),
            ],
        )
        def test_is_dict(input_value, expected):
    >       assert input_value == expected
    E       assert {1: 10, 2: False} == IsIgnoreDict[ignore={False}](1=10)
    
    tests/test_dict.py:47: AssertionError
    ________________________________________________________ test_callable_ignore _________________________________________________________
    
        def test_callable_ignore():
            assert {'a': 1} == IsDict(a=1).settings(ignore=ignore_42)
    >       assert {'a': 1, 'b': 42} == IsDict(a=1).settings(ignore=ignore_42)
    E       AssertionError: assert {'a': 1, 'b': 42} == IsDict[ignore=ignore_42](a=1)
    E        +  where IsDict[ignore=ignore_42](a=1) = <bound method IsDict.settings of IsDict(a=1)>(ignore=ignore_42)
    E        +    where <bound method IsDict.settings of IsDict(a=1)> = IsDict(a=1).settings
    E        +      where IsDict(a=1) = IsDict(a=1)
    
    tests/test_dict.py:95: AssertionError
    _____________________________________________________________ test_ignore _____________________________________________________________
    
        def test_ignore():
            def custom_ignore(v: int) -> bool:
                return v % 2 == 0
        
    >       assert {'a': 1, 'b': 2, 'c': 3, 'd': 4} == IsDict(a=1, c=3).settings(ignore=custom_ignore)
    E       AssertionError: assert {'a': 1, 'b': 2, 'c': 3, 'd': 4} == IsDict[ignore=custom_ignore](a=1, c=3)
    E        +  where IsDict[ignore=custom_ignore](a=1, c=3) = <bound method IsDict.settings of IsDict(a=1, c=3)>(ignore=<function test_ignore.<locals>.custom_ignore at 0x00007f313d03a020>)
    E        +    where <bound method IsDict.settings of IsDict(a=1, c=3)> = IsDict(a=1, c=3).settings
    E        +      where IsDict(a=1, c=3) = IsDict(a=1, c=3)
    
    tests/test_dict.py:129: AssertionError
    _______________________________________________________ test_ignore_with_is_str _______________________________________________________
    
        def test_ignore_with_is_str():
            api_data = {'id': 123, 'token': 't-abc123', 'dob': None, 'street_address': None}
        
            token_is_str = IsStr(regex=r't\-.+')
    >       assert api_data == IsIgnoreDict(id=IsPositiveInt, token=token_is_str)
    E       AssertionError: assert {'dob': None, 'id': 123, 'street_address': None, 'token': 't-abc123'} == IsIgnoreDict(id=IsPositiveInt, token=IsStr(regex='t\\-.+'))
    E        +  where IsIgnoreDict(id=IsPositiveInt, token=IsStr(regex='t\\-.+')) = IsIgnoreDict(id=IsPositiveInt, token=IsStr(regex='t\\-.+'))
    
    tests/test_dict.py:136: AssertionError
    ________________________________________________________ test_unhashable_value ________________________________________________________
    
        def test_unhashable_value():
            a = {'a': 1}
            api_data = {'b': a, 'c': None}
    >       assert api_data == IsIgnoreDict(b=a)
    E       AssertionError: assert {'b': {'a': 1}, 'c': None} == IsIgnoreDict(b={'a': 1})
    E        +  where IsIgnoreDict(b={'a': 1}) = IsIgnoreDict(b={'a': 1})
    
    tests/test_dict.py:143: AssertionError
    _______________________________________ test_docs_examples[dirty_equals/_inspection.py:172-189] _______________________________________
    
    module_name = '_inspection_172_189'
    source_code = '\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\...IsStr)\nassert Foo(1, 2) != HasAttributes(a=1, b=2, c=3)\nassert Foo(1, 2) == HasAttributes(a=1, b=2, spam=AnyThing)\n'
    import_execute = <function import_execute.<locals>._import_execute at 0x00007f313da302a0>
    
        def test_docs_examples(module_name, source_code, import_execute):
    >       import_execute(module_name, source_code, True)
    
    tests/test_docs.py:69: 
    _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    tests/test_docs.py:25: in _import_execute
        spec.loader.exec_module(module)
    /usr/lib/pypy3.9/site-packages/_pytest/assertion/rewrite.py:168: in exec_module
        exec(co, module.__dict__)
    _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    
        from dirty_equals import HasAttributes, IsInt, IsStr, AnyThing
        
        class Foo:
            def __init__(self, a, b):
                self.a = a
                self.b = b
        
            def spam(self):
                pass
        
        assert Foo(1, 2) == HasAttributes(a=1, b=2)
        assert Foo(1, 2) == HasAttributes(a=1)
    >   assert Foo(1, 's') == HasAttributes(a=IsInt, b=IsStr)
    E   AssertionError: assert <_inspection_172_189.Foo object at 0x00007f313d70b750> == HasAttributes(a=IsInt, b=IsStr)
    E    +  where <_inspection_172_189.Foo object at 0x00007f313d70b750> = <class '_inspection_172_189.Foo'>(1, 's')
    E    +  and   HasAttributes(a=IsInt, b=IsStr) = HasAttributes(a=IsInt, b=IsStr)
    
    ../pytest-of-mgorny/pytest-15/test_docs_examples_dirty_equal26/_inspection_172_189.py:185: AssertionError
    __________________________________________ test_docs_examples[dirty_equals/_dict.py:186-204] __________________________________________
    
    module_name = '_dict_186_204'
    source_code = "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\...(a=1, c=3).settings(strict=True)\nassert {'b': None, 'c': 3, 'a': 1} != IsIgnoreDict(a=1, c=3).settings(strict=True)\n"
    import_execute = <function import_execute.<locals>._import_execute at 0x00007f313f1be2a0>
    
        def test_docs_examples(module_name, source_code, import_execute):
    >       import_execute(module_name, source_code, True)
    
    tests/test_docs.py:69: 
    _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    tests/test_docs.py:25: in _import_execute
        spec.loader.exec_module(module)
    /usr/lib/pypy3.9/site-packages/_pytest/assertion/rewrite.py:168: in exec_module
        exec(co, module.__dict__)
    _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    
        from dirty_equals import IsIgnoreDict
        
    >   assert {'a': 1, 'b': 2, 'c': None} == IsIgnoreDict(a=1, b=2)
    E   AssertionError: assert {'a': 1, 'b': 2, 'c': None} == IsIgnoreDict(a=1, b=2)
    E    +  where IsIgnoreDict(a=1, b=2) = IsIgnoreDict(a=1, b=2)
    
    ../pytest-of-mgorny/pytest-15/test_docs_examples_dirty_equal30/_dict_186_204.py:189: AssertionError
    ________________________________________ test_has_attributes[-HasAttributes(a=IsInt, b=IsStr)] ________________________________________
    
    value = <tests.test_inspection.Foo object at 0x00007f313eb0bc20>, dirty = HasAttributes(a=IsInt, b=IsStr)
    
        @pytest.mark.parametrize(
            'value,dirty',
            [
                (Foo(1, 2), HasAttributes(a=1, b=2)),
                (Foo(1, 's'), HasAttributes(a=IsInt, b=IsStr)),
                (Foo(1, 2), ~HasAttributes(a=IsInt, b=IsStr)),
                (Foo(1, 2), ~HasAttributes(a=1, b=2, c=3)),
                (Foo(1, 2), HasAttributes(a=1, b=2, spam=AnyThing)),
                (Foo(1, 2), ~HasAttributes(a=1, b=2, missing=AnyThing)),
            ],
            ids=dirty_repr,
        )
        def test_has_attributes(value, dirty):
    >       assert value == dirty
    E       assert <tests.test_inspection.Foo object at 0x00007f313eb0bc20> == HasAttributes(a=IsInt, b=IsStr)
    
    tests/test_inspection.py:86: AssertionError
    ======================================================= short test summary info =======================================================
    FAILED tests/test_base.py::test_not_repr - Failed: DID NOT RAISE <class 'AssertionError'>
    FAILED tests/test_boolean.py::test_dirty_not_equals - Failed: DID NOT RAISE <class 'AssertionError'>
    FAILED tests/test_dict.py::test_is_dict[input_value16-expected16] - AssertionError: assert {'a': 1, 'b': None} == IsIgnoreDict(a=1)
    FAILED tests/test_dict.py::test_is_dict[input_value17-expected17] - assert {1: 10, 2: None} == IsIgnoreDict(1=10)
    FAILED tests/test_dict.py::test_is_dict[input_value20-expected20] - assert {1: 10, 2: False} == IsIgnoreDict[ignore={False}](1=10)
    FAILED tests/test_dict.py::test_callable_ignore - AssertionError: assert {'a': 1, 'b': 42} == IsDict[ignore=ignore_42](a=1)
    FAILED tests/test_dict.py::test_ignore - AssertionError: assert {'a': 1, 'b': 2, 'c': 3, 'd': 4} == IsDict[ignore=custom_ignore](a=1...
    FAILED tests/test_dict.py::test_ignore_with_is_str - AssertionError: assert {'dob': None, 'id': 123, 'street_address': None, 'token'...
    FAILED tests/test_dict.py::test_unhashable_value - AssertionError: assert {'b': {'a': 1}, 'c': None} == IsIgnoreDict(b={'a': 1})
    FAILED tests/test_docs.py::test_docs_examples[dirty_equals/_inspection.py:172-189] - AssertionError: assert <_inspection_172_189.Foo...
    FAILED tests/test_docs.py::test_docs_examples[dirty_equals/_dict.py:186-204] - AssertionError: assert {'a': 1, 'b': 2, 'c': None} ==...
    FAILED tests/test_inspection.py::test_has_attributes[-HasAttributes(a=IsInt, b=IsStr)] - assert <tests.test_inspection.Foo object at...
    =================================================== 12 failed, 472 passed in 3.99s ====================================================
    
  • Make IsNow relative to current moment of time

    Make IsNow relative to current moment of time

    Issue: https://github.com/samuelcolvin/dirty-equals/issues/39

    Implemented logic that updates theapprox value of IsNow for every comparison. Other ideas, that I've considered:

    • set the approx value to None and generate now on every comparison. Downside -- hard to make repr
    • set the approx value to the current time on __ini__ and do not use it for comparison. Downside -- repr will be lying.
    • Remove internal state from IsNow and on every compassion create an instance of class DateTime and use it for comparing with other value. More work, so I started from the easiest one.

    The downside of my implementation is that the internal state is changing for every comparison, instead of not having a state at all.

  • Include documentation on how this works

    Include documentation on how this works

    I find this functionality quite interesting and think it could be useful. But I'm a bit hesitant to include a library with "magical" behavior that I don't understand.

    My main question is, why is the __eq__ method of the right operand used? The python documentation seems to suggest that x == y should call x.__eq__(y).

    If the operands are of different types, and right operandโ€™s type is a direct or indirect subclass of the left operandโ€™s type, the reflected method of the right operand has priority, otherwise the left operandโ€™s method has priority.

    The last paragraph in the docs above point me to the __eq__ implementation in the DirtyEqualsMeta class. But I'm not sure what is going on.

  • Some ideas about partial lists

    Some ideas about partial lists

    Hi @samuelcolvin

    This looks really exciting, I think a library like this should have been created a long time ago :slightly_smiling_face:

    Some time ago I played around with similar idea of partial data equality checks to simplify test assertions and wrote a small library for this. I just wanted to share a couple of thoughts that you might find interesting. (Or maybe you already considered this, in that case - sorry for bothering)

    In my experience a very common and annoying problem is when you need to ensure that a list contains all expected elements but their order is unknown and you don't care about order anyway. When elements are not hashable and not comparable you can't just sort two lists or turn them into sets. For example:

    expected_result = [{"id": 1, "name": "John"}, {"id": 2, "name": "Jane"}]
    assert result == expected_result # can't just do that
    

    Possible workaround might be something like this:

    sorted(result, key=itemgetter("id")) == sorted(expected_result, key=itemgetter("id"))
    

    Or perhaps a helper function:

    def unordered_equals(first: list, second: list) -> bool:
        return all(el in second for el in first) and all(el in first for el in second)
    

    I think it would be great to have a simple way for doing such checks.

    Also, sometimes I need to check only specific element(s) in a collection. I often notice that I want to be able to do something like this:

    [1, 2, 3] == StartsWith([1, 2])
    [1, 2, 3] == Contains(3)
    
    # We only need to check that a user has a post with id=42
    response == IsPartialDict({
        "username": "John",
        "posts": Contains(IsPartialDict(id=42)),
    })
    
  • pytz version requirement

    pytz version requirement

    Is there a reason that dirty-equals requires a semver version of pytz or would you be happy to accept a PR to change the requirement to *?

    I assume it was just added with poetry add or something.

  • `typing` compatibility

    `typing` compatibility

    I was trying out this cool new library and noticed some mypy errors on python 3.9.9 after cloning:

    $ make mypy
    mypy --show-error-codes dirty_equals
    dirty_equals/_base.py:13: error: Module "typing" has no attribute "TypeAlias"  [attr-defined]
    dirty_equals/_base.py:32: error: Unused "type: ignore" comment
    dirty_equals/_sequence.py:7: error: Module "typing" has no attribute "TypeAlias"  [attr-defined]
    

    this pr is a suggestion to add a _compat.typing module for handling the use of backports when the python version requires them https://mypy.readthedocs.io/en/stable/common_issues.html#python-version-and-system-platform-checks

  • Idea: generate is now for IsNow only at moment when comparing

    Idea: generate is now for IsNow only at moment when comparing

    My use case is pytest with parametrize method. In such situation, IsNow() will compare time generated when pytest will collect tests and now time when tests were run.

    @pytest.mark.parametrize(
        "params, expected",
        [
            (
               {'some_key': 'some_value'},
               IsNow(),
            ),
            ...,
        ],
    )
    def test_handler(
        client,
        params
        expected,
    ):
        response = make_request(params)
        response['time'] = expected
    
  • IsDict - Allow ignoring keys

    IsDict - Allow ignoring keys

    Hi,

    I am loving this library ๐Ÿ˜

    I am using it to test my FastAPI endpoint and I want to ignore the default columns like is_active (also id) because I have something like below:

    assert [{'id': 1, 'name': 'john'}] == IsList(IsPartialDict(name='john', is_active = True})
    

    Certainly, I can use .settings(ignore={True}) but I fear it might ignore other columns with the value True in the future.

    Right now, the above assertion will fail but with ignore_keys we will be able to ignore keys safely.

    P.S. I love to make a PR but I checked the source code and I think I shouldn't touch it.

  • unix datetime tests fail if TZ != UTC

    unix datetime tests fail if TZ != UTC

    The following tests fail if the system timezone is not UTC:

    FAILED tests/test_datetime.py::test_is_datetime[unix-int] - assert 946684800 == IsDatetime(approx=datetime.datetime(2000, 1, 1, 0, 0...
    FAILED tests/test_datetime.py::test_is_datetime[unix-float] - assert 946684800.123 == IsDatetime(approx=datetime.datetime(2000, 1, 1...
    FAILED tests/test_docs.py::test_docs_examples[dirty_equals/_datetime.py:45-58] - assert 946684800.123 == IsDatetime(approx=datetime....
    

    Full output:

    ============================================================== FAILURES ===============================================================
    _____________________________________________________ test_is_datetime[unix-int] ______________________________________________________
    
    value = 946684800, dirty = IsDatetime(approx=datetime.datetime(2000, 1, 1, 0, 0), unix_number=True), expect_match = True
    
        @pytest.mark.parametrize(
            'value,dirty,expect_match',
            [
                pytest.param(datetime(2000, 1, 1), IsDatetime(approx=datetime(2000, 1, 1)), True, id='same'),
                # Note: this requires the system timezone to be UTC
                pytest.param(946684800, IsDatetime(approx=datetime(2000, 1, 1), unix_number=True), True, id='unix-int'),
                # Note: this requires the system timezone to be UTC
                pytest.param(946684800.123, IsDatetime(approx=datetime(2000, 1, 1), unix_number=True), True, id='unix-float'),
                pytest.param(946684800, IsDatetime(approx=datetime(2000, 1, 1)), False, id='unix-different'),
                pytest.param(
                    '2000-01-01T00:00', IsDatetime(approx=datetime(2000, 1, 1), iso_string=True), True, id='iso-string-true'
                ),
                pytest.param('2000-01-01T00:00', IsDatetime(approx=datetime(2000, 1, 1)), False, id='iso-string-different'),
                pytest.param('broken', IsDatetime(approx=datetime(2000, 1, 1)), False, id='iso-string-wrong'),
                pytest.param(
                    '28/01/87', IsDatetime(approx=datetime(1987, 1, 28), format_string='%d/%m/%y'), True, id='string-format'
                ),
                pytest.param('28/01/87', IsDatetime(approx=datetime(2000, 1, 1)), False, id='string-format-different'),
                pytest.param('foobar', IsDatetime(approx=datetime(2000, 1, 1)), False, id='string-format-wrong'),
                pytest.param(datetime.now().isoformat(), IsNow(iso_string=True), True, id='isnow-str-true'),
                pytest.param(datetime(2000, 1, 1).isoformat(), IsNow(iso_string=True), False, id='isnow-str-different'),
                pytest.param([1, 2, 3], IsDatetime(approx=datetime(2000, 1, 1)), False, id='wrong-type'),
                pytest.param(
                    datetime(2020, 1, 1, 12, 13, 14), IsDatetime(approx=datetime(2020, 1, 1, 12, 13, 14)), True, id='tz-same'
                ),
                pytest.param(
                    datetime(2020, 1, 1, 12, 13, 14, tzinfo=timezone.utc),
                    IsDatetime(approx=datetime(2020, 1, 1, 12, 13, 14), enforce_tz=False),
                    True,
                    id='tz-utc',
                ),
                pytest.param(
                    datetime(2020, 1, 1, 12, 13, 14, tzinfo=timezone.utc),
                    IsDatetime(approx=datetime(2020, 1, 1, 12, 13, 14)),
                    False,
                    id='tz-utc-different',
                ),
                pytest.param(
                    datetime(2020, 1, 1, 12, 13, 14),
                    IsDatetime(approx=datetime(2020, 1, 1, 12, 13, 14, tzinfo=timezone.utc), enforce_tz=False),
                    False,
                    id='tz-approx-tz',
                ),
                pytest.param(
                    datetime(2020, 1, 1, 12, 13, 14, tzinfo=timezone(offset=timedelta(hours=1))),
                    IsDatetime(approx=datetime(2020, 1, 1, 12, 13, 14), enforce_tz=False),
                    True,
                    id='tz-1-hour',
                ),
                pytest.param(
                    pytz.timezone('Europe/London').localize(datetime(2022, 2, 15, 15, 15)),
                    IsDatetime(
                        approx=pytz.timezone('America/New_York').localize(datetime(2022, 2, 15, 10, 15)), enforce_tz=False
                    ),
                    True,
                    id='tz-both-tz',
                ),
                pytest.param(
                    pytz.timezone('Europe/London').localize(datetime(2022, 2, 15, 15, 15)),
                    IsDatetime(approx=pytz.timezone('America/New_York').localize(datetime(2022, 2, 15, 10, 15))),
                    False,
                    id='tz-both-tz-different',
                ),
                pytest.param(datetime(2000, 1, 1), IsDatetime(ge=datetime(2000, 1, 1)), True, id='ge'),
                pytest.param(datetime(1999, 1, 1), IsDatetime(ge=datetime(2000, 1, 1)), False, id='ge-not'),
                pytest.param(datetime(2000, 1, 2), IsDatetime(gt=datetime(2000, 1, 1)), True, id='gt'),
                pytest.param(datetime(2000, 1, 1), IsDatetime(gt=datetime(2000, 1, 1)), False, id='gt-not'),
            ],
        )
        def test_is_datetime(value, dirty, expect_match):
            if expect_match:
    >           assert value == dirty
    E           assert 946684800 == IsDatetime(approx=datetime.datetime(2000, 1, 1, 0, 0), unix_number=True)
    
    tests/test_datetime.py:80: AssertionError
    ____________________________________________________ test_is_datetime[unix-float] _____________________________________________________
    
    value = 946684800.123, dirty = IsDatetime(approx=datetime.datetime(2000, 1, 1, 0, 0), unix_number=True), expect_match = True
    
        @pytest.mark.parametrize(
            'value,dirty,expect_match',
            [
                pytest.param(datetime(2000, 1, 1), IsDatetime(approx=datetime(2000, 1, 1)), True, id='same'),
                # Note: this requires the system timezone to be UTC
                pytest.param(946684800, IsDatetime(approx=datetime(2000, 1, 1), unix_number=True), True, id='unix-int'),
                # Note: this requires the system timezone to be UTC
                pytest.param(946684800.123, IsDatetime(approx=datetime(2000, 1, 1), unix_number=True), True, id='unix-float'),
                pytest.param(946684800, IsDatetime(approx=datetime(2000, 1, 1)), False, id='unix-different'),
                pytest.param(
                    '2000-01-01T00:00', IsDatetime(approx=datetime(2000, 1, 1), iso_string=True), True, id='iso-string-true'
                ),
                pytest.param('2000-01-01T00:00', IsDatetime(approx=datetime(2000, 1, 1)), False, id='iso-string-different'),
                pytest.param('broken', IsDatetime(approx=datetime(2000, 1, 1)), False, id='iso-string-wrong'),
                pytest.param(
                    '28/01/87', IsDatetime(approx=datetime(1987, 1, 28), format_string='%d/%m/%y'), True, id='string-format'
                ),
                pytest.param('28/01/87', IsDatetime(approx=datetime(2000, 1, 1)), False, id='string-format-different'),
                pytest.param('foobar', IsDatetime(approx=datetime(2000, 1, 1)), False, id='string-format-wrong'),
                pytest.param(datetime.now().isoformat(), IsNow(iso_string=True), True, id='isnow-str-true'),
                pytest.param(datetime(2000, 1, 1).isoformat(), IsNow(iso_string=True), False, id='isnow-str-different'),
                pytest.param([1, 2, 3], IsDatetime(approx=datetime(2000, 1, 1)), False, id='wrong-type'),
                pytest.param(
                    datetime(2020, 1, 1, 12, 13, 14), IsDatetime(approx=datetime(2020, 1, 1, 12, 13, 14)), True, id='tz-same'
                ),
                pytest.param(
                    datetime(2020, 1, 1, 12, 13, 14, tzinfo=timezone.utc),
                    IsDatetime(approx=datetime(2020, 1, 1, 12, 13, 14), enforce_tz=False),
                    True,
                    id='tz-utc',
                ),
                pytest.param(
                    datetime(2020, 1, 1, 12, 13, 14, tzinfo=timezone.utc),
                    IsDatetime(approx=datetime(2020, 1, 1, 12, 13, 14)),
                    False,
                    id='tz-utc-different',
                ),
                pytest.param(
                    datetime(2020, 1, 1, 12, 13, 14),
                    IsDatetime(approx=datetime(2020, 1, 1, 12, 13, 14, tzinfo=timezone.utc), enforce_tz=False),
                    False,
                    id='tz-approx-tz',
                ),
                pytest.param(
                    datetime(2020, 1, 1, 12, 13, 14, tzinfo=timezone(offset=timedelta(hours=1))),
                    IsDatetime(approx=datetime(2020, 1, 1, 12, 13, 14), enforce_tz=False),
                    True,
                    id='tz-1-hour',
                ),
                pytest.param(
                    pytz.timezone('Europe/London').localize(datetime(2022, 2, 15, 15, 15)),
                    IsDatetime(
                        approx=pytz.timezone('America/New_York').localize(datetime(2022, 2, 15, 10, 15)), enforce_tz=False
                    ),
                    True,
                    id='tz-both-tz',
                ),
                pytest.param(
                    pytz.timezone('Europe/London').localize(datetime(2022, 2, 15, 15, 15)),
                    IsDatetime(approx=pytz.timezone('America/New_York').localize(datetime(2022, 2, 15, 10, 15))),
                    False,
                    id='tz-both-tz-different',
                ),
                pytest.param(datetime(2000, 1, 1), IsDatetime(ge=datetime(2000, 1, 1)), True, id='ge'),
                pytest.param(datetime(1999, 1, 1), IsDatetime(ge=datetime(2000, 1, 1)), False, id='ge-not'),
                pytest.param(datetime(2000, 1, 2), IsDatetime(gt=datetime(2000, 1, 1)), True, id='gt'),
                pytest.param(datetime(2000, 1, 1), IsDatetime(gt=datetime(2000, 1, 1)), False, id='gt-not'),
            ],
        )
        def test_is_datetime(value, dirty, expect_match):
            if expect_match:
    >           assert value == dirty
    E           assert 946684800.123 == IsDatetime(approx=datetime.datetime(2000, 1, 1, 0, 0), unix_number=True)
    
    tests/test_datetime.py:80: AssertionError
    _________________________________________ test_docs_examples[dirty_equals/_datetime.py:45-58] _________________________________________
    
    module_name = '_datetime_45_58'
    source_code = '\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nfrom dirty_equals import Is...string=True)\n\nassert datetime(2000, 1, 2) == IsDatetime(gt=y2k)\nassert datetime(1999, 1, 2) != IsDatetime(gt=y2k)\n'
    import_execute = <function import_execute.<locals>._import_execute at 0x7f3d4b0a4dc0>
    
        def test_docs_examples(module_name, source_code, import_execute):
    >       import_execute(module_name, source_code, True)
    
    tests/test_docs.py:69: 
    _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    tests/test_docs.py:25: in _import_execute
        spec.loader.exec_module(module)
    /usr/lib/python3.10/site-packages/_pytest/assertion/rewrite.py:168: in exec_module
        exec(co, module.__dict__)
    _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    
        from dirty_equals import IsDatetime
        from datetime import datetime
        
        y2k = datetime(2000, 1, 1)
        assert datetime(2000, 1, 1) == IsDatetime(approx=y2k)
        # Note: this requires the system timezone to be UTC
        assert 946684800.123 == IsDatetime(approx=y2k, unix_number=True)
    E   assert 946684800.123 == IsDatetime(approx=datetime.datetime(2000, 1, 1, 0, 0), unix_number=True)
    E    +  where IsDatetime(approx=datetime.datetime(2000, 1, 1, 0, 0), unix_number=True) = IsDatetime(approx=datetime.datetime(2000, 1, 1, 0, 0), unix_number=True)
    
    ../pytest-of-mgorny/pytest-9/test_docs_examples_dirty_equal32/_datetime_45_58.py:52: AssertionError
    ======================================================= short test summary info =======================================================
    FAILED tests/test_datetime.py::test_is_datetime[unix-int] - assert 946684800 == IsDatetime(approx=datetime.datetime(2000, 1, 1, 0, 0...
    FAILED tests/test_datetime.py::test_is_datetime[unix-float] - assert 946684800.123 == IsDatetime(approx=datetime.datetime(2000, 1, 1...
    FAILED tests/test_docs.py::test_docs_examples[dirty_equals/_datetime.py:45-58] - assert 946684800.123 == IsDatetime(approx=datetime....
    ==================================================== 3 failed, 481 passed in 1.09s ====================================================
    
  • custom `pytest_assertrepr_compare`

    custom `pytest_assertrepr_compare`

    Approach:

    • new method on some types, e.g. __inequality_details__(op) should return a list of strings
    • add a utility function to check for __inequality_details__ and call it if required,
    • instructions on calling that function from pytest_assertrepr_compare

    rough prototype for Contains:

    def pytest_assertrepr_compare(config, op, left, right):
        if isinstance(right, Contains):
            word = 'not' if op == '==' else 'unexpectedly'
            if len(right.contained_values) == 1:
                descr = f'Container: {right.contained_values[0]!r} {word} found in'
            else:
                descr = f'Container: {right.contained_values!r} all {word} found in'
            return [
                f'[dirty-equals] "{op}" failed',
                descr,
                *pformat(left).split('\n'),
            ]
    
  • More types

    More types

    • [x] IsFalseLike #23
    • [x] IsTrueLike #25
    • [x] IsPartialDict #5
    • [x] IsFullDict(order_matters=False) #5
    • [x] IsIterable(*items, start=None, finish=None, order_matters=True, len=None) #7
    • [x] IsList(*items, skip=DontSkip, order_matters=True, len=None) #7
    • [x] IsTuple(*items, skip=DontSkip, order_matters=True, len=None) #7
    • [x] IsPartialIterable(items: Dict[int, Any]) #7
    • [x] IsStr(regex, min_length, max_length, upper, lower, digits)
    • [x] IsBytes(regex, min_length, max_length, upper, lower, digits)
    • [x] IsToday #20
    • [x] HasProperties #26
    • [x] HasName #26
    • [x] HasRepr #26
    • [x] HasLen #7
    • [x] IsOneOf
    • [x] EqualsFunc #4
    • [x] IsJson #4
    • [ ] IsDataclass
    • [ ] IsEnumMember(v, strict=True)
Useful additions to Django's default TestCase
Useful additions to Django's default TestCase

django-test-plus Useful additions to Django's default TestCase from REVSYS Rationale Let's face it, writing tests isn't always fun. Part of the reason

May 18, 2022
Useful additions to Django's default TestCase
Useful additions to Django's default TestCase

django-test-plus Useful additions to Django's default TestCase from REVSYS Rationale Let's face it, writing tests isn't always fun. Part of the reason

May 18, 2022
A folder automation made using Watch-dog, it only works in linux for now but I assume, it will be adaptable to mac and PC as well

folder-automation A folder automation made using Watch-dog, it only works in linux for now but I assume, it will be adaptable to mac and PC as well Th

May 28, 2021
Selenium-python but lighter: Helium is the best Python library for web automation.
Selenium-python but lighter: Helium is the best Python library for web automation.

Selenium-python but lighter: Helium Selenium-python is great for web automation. Helium makes it easier to use. For example: Under the hood, Helium fo

May 20, 2022
A rewrite of Python's builtin doctest module (with pytest plugin integration) but without all the weirdness
A rewrite of Python's builtin doctest module (with pytest plugin integration) but without all the weirdness

The xdoctest package is a re-write of Python's builtin doctest module. It replaces the old regex-based parser with a new abstract-syntax-tree based pa

May 7, 2022
Fully Automated YouTube Channel โ–ถ๏ธwith Added Extra Features.

Fully Automated Youtube Channel โ–’โ–ˆโ–€โ–€โ–ˆ โ–ˆโ–€โ–€โ–ˆ โ–€โ–€โ–ˆโ–€โ–€ โ–€โ–€โ–ˆโ–€โ–€ โ–ˆโ–‘โ–‘โ–ˆ โ–ˆโ–€โ–€โ–„ โ–ˆโ–€โ–€ โ–ˆโ–€โ–€โ–ˆ โ–’โ–ˆโ–€โ–€โ–„ โ–ˆโ–‘โ–‘โ–ˆ โ–‘โ–‘โ–ˆโ–‘โ–‘ โ–‘โ–’โ–ˆโ–‘โ–‘ โ–ˆโ–‘โ–‘โ–ˆ โ–ˆโ–€โ–€โ–„ โ–ˆโ–€โ–€ โ–ˆโ–„โ–„โ–€ โ–’โ–ˆโ–„โ–„โ–ˆ โ–€โ–€โ–€โ–€ โ–‘โ–‘โ–€โ–‘โ–‘ โ–‘โ–’โ–ˆโ–‘โ–‘ โ–‘โ–€โ–€โ–€ โ–€โ–€โ–€โ–‘

May 14, 2022
A guy with a lot of useful things to do when doing AtCoder in Python

atcoder_python_env Python ใง AtCoder ใ‚’ใ‚„ใ‚‹ใจใใซไพฟๅˆฉใช่ซธใ€…ใ‚’็”จๆ„ใ—ใŸใ‚„ใค ใ‚ณใƒณใƒ†ใ‚นใƒˆ็”จใƒ•ใ‚ฉใƒซใƒ€ใฎไฝœๆˆ ใ‚ปใƒƒใƒˆใ‚ขใƒƒใƒ— ่‡ชๅ‹•ใƒ†ใ‚น

Dec 28, 2021
Dirty, ugly, and hopefully useful OCR of Facebook Papers docs released by Gizmodo

Quick and Dirty OCR of Facebook Papers Gizmodo has been working through the Facebook Papers and releasing the docs that they process and review. As lu

Oct 28, 2021
All of the ad-hoc things you're doing to manage incidents today, done for you, and much more!

About What's Dispatch? Put simply, Dispatch is: All of the ad-hoc things youโ€™re doing to manage incidents today, done for you, and a bunch of other th

May 13, 2022
Python library for doing things with Grid-like structures

gridthings Python library for doing things with Grid-like structures Development This project uses poetry for dependency management, pre-commit for li

Dec 21, 2021
A bot for PDF for doing Many Things....

Telegram PDF Bot A Telegram bot that can: Compress, crop, decrypt, encrypt, merge, preview, rename, rotate, scale and split PDF files Compare text dif

May 11, 2022
I'm doing Genuary, an aritifiacilly generated month to build code that make beautiful things
I'm doing Genuary, an aritifiacilly generated month to build code that make beautiful things

Genuary 2022 I'm doing Genuary, an aritifiacilly generated month to build code that make beautiful things. Every day there is a new prompt for making

Jan 10, 2022
A modern python module including many useful features that make discord bot programming extremely easy.

discord-super-utils Documentation Secondary Documentation A modern python module including many useful features that make discord bot programming extr

May 12, 2022
A logical, reasonably standardized, but flexible project structure for doing and sharing data science work.
A logical, reasonably standardized, but flexible project structure for doing and sharing data science work.

Cookiecutter Data Science A logical, reasonably standardized, but flexible project structure for doing and sharing data science work. Project homepage

Sep 5, 2021
One-line your code easily but still with the fun of doing so!

One-liner-iser One-line your code easily but still with the fun of doing so! Have YOU ever wanted to write one-line Python code, but don't have the sa

May 4, 2022
It is a useful project for developers that includes useful tools for Instagram

InstagramIG It is a useful project for developers that includes useful tools for Instagram Installation : pip install InstagramIG Logan Usage from In

Mar 14, 2022
A (very dirty) experiment to remove layers from a Docker image.

Surgically remove layers from a Docker image (with a chainsaw)

Jan 22, 2022
Quick and dirty script to fix MD5 hashes in poetry.lock file

fix-poetry-md5-hash Quick and dirty script to fix MD5 hashes in poetry.lock file. Usage: poetry run fix-poetry-md5-hash

Apr 20, 2022
A quick and dirty QT Statusbar implementation for grabbing GIFs from Tenor, since there is no offical or unofficial one I found. This was intended for use under Linux, however it was also functional enough on MacOS.
A quick and dirty QT Statusbar implementation for grabbing GIFs from Tenor, since there is no offical or unofficial one I found. This was intended for use under Linux, however it was also functional enough on MacOS.

Statusbar-TenorGIF App for Linux A quick and dirty QT Statusbar implementation for grabbing GIFs from Tenor, since there is no offical one and I didnt

Nov 1, 2021
A quick and dirty script to scan the network, find default credentials on services and post a message to a Slack channel with the results.

A quick and dirty script to scan the network, find default credentials on services and post a message to a Slack channel with the results.

Jan 23, 2022