Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ jobs:
- '3.8'
- '3.9'
- '3.10'
- '3.11'

steps:
- uses: actions/checkout@v2
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ __pycache__/
/.tox_docker
/.eggs
/.coverage
/.coverage.*
/.pytest_cache
/build
/dist
Expand Down
13 changes: 7 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# Settings
# NOTE: The multi-python image is a fork of fkrull/multi-python, which as of now has not been updated for Python 3.10 yet
DOCKER_MULTI_PYTHON_IMAGE = gnufede/multi-python:focal
DOCKER_MULTI_PYTHON_IMAGE = acidrain/multi-python:latest
DOCKER_USER = "$(shell id -u):$(shell id -g)"

# Default target
Expand Down Expand Up @@ -38,7 +37,7 @@ venv-tox:
# Only run pytest
.PHONY: test
test:
tox -e 'clean,py{310,39,38,37},report'
tox -e 'clean,py{311,310,39,38,37},report'

# Only run flake8 linter
.PHONY: flake8
Expand All @@ -62,7 +61,9 @@ docker-tox:
tox --workdir .tox_docker $(TOX_ARGS)

# Run partial tox test suites in Docker
.PHONY: docker-tox-py310 docker-tox-py39 docker-tox-py38 docker-tox-py37
.PHONY: docker-tox-py311 docker-tox-py310 docker-tox-py39 docker-tox-py38 docker-tox-py37
docker-tox-py311: TOX_ARGS="-e clean,py311,py311-report"
docker-tox-py311: docker-tox
docker-tox-py310: TOX_ARGS="-e clean,py310,py310-report"
docker-tox-py310: docker-tox
docker-tox-py39: TOX_ARGS="-e clean,py39,py39-report"
Expand All @@ -79,6 +80,7 @@ docker-tox-all:
make docker-tox-py38
make docker-tox-py39
make docker-tox-py310
make docker-tox-py311

# Pull the latest image of the multi-python Docker image
.PHONY: docker-pull
Expand All @@ -91,12 +93,11 @@ docker-pull:

.PHONY: clean
clean:
rm -rf .coverage .pytest_cache reports src/validataclass/_version.py
rm -rf .coverage .pytest_cache reports src/validataclass/_version.py .tox .tox_docker .eggs src/*.egg-info venv

.PHONY: clean-dist
clean-dist:
rm -rf dist/

.PHONY: clean-all
clean-all: clean clean-dist
rm -rf .tox .tox_docker .eggs src/*.egg-info venv
1 change: 1 addition & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ classifiers =
Programming Language :: Python :: 3.8
Programming Language :: Python :: 3.9
Programming Language :: Python :: 3.10
Programming Language :: Python :: 3.11
Programming Language :: Python :: Implementation :: CPython
Topic :: Software Development :: Libraries :: Python Modules
Topic :: Utilities
Expand Down
10 changes: 10 additions & 0 deletions src/validataclass/dataclasses/defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ def __eq__(self, other):
return self.value == other.value
return NotImplemented

def __hash__(self):
return hash(self.value)

def get_value(self) -> Any:
return deepcopy(self.value)

Expand Down Expand Up @@ -75,6 +78,9 @@ def __eq__(self, other):
return isinstance(other, DefaultFactory) and self.factory == other.factory
return NotImplemented

def __hash__(self):
return hash(self.factory)

def get_value(self) -> Any:
return self.factory()

Expand Down Expand Up @@ -127,6 +133,10 @@ def __eq__(self, other):
# Nothing is equal to NoDefault except itself
return type(self) is type(other)

def __hash__(self):
# Use default implementation
return object.__hash__(self)

def get_value(self) -> NoReturn:
raise ValueError('No default value specified!')

Expand Down
4 changes: 2 additions & 2 deletions src/validataclass/helpers/unset_value.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ def __str__(self):
def __bool__(self):
return False

def __eq__(self, other):
return other is self
# Don't define __eq__ because the default implementation is fine (identity check), and because we would then have to

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then why you don't remove the line?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if I got your question ^^' You mean why I put the comment there instead of just removing the code?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, exactly :D

Copy link
Contributor Author

@binaryDiv binaryDiv May 22, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mainly as a reminder for future me who might possibly see this class and say "hmm, why didn't we define __eq__ here, maybe I should add that, that sounds like a good idea" 😅
(On the other hand, the unit test would fail again then, so... shrug)

# implement __hash__ as well, otherwise UnsetValue would be considered mutable by @dataclass.


# Create sentinel object and redefine __new__ so that the object cannot be cloned
Expand Down
16 changes: 16 additions & 0 deletions tests/dataclasses/defaults_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,12 @@ def test_default_non_equality(first, second):
assert first != second
assert second != first

@staticmethod
@pytest.mark.parametrize('value', [None, 0, 42, 'banana'])
def test_default_hashable(value):
""" Test hashability (__hash__) of Default objects. """
assert hash(Default(value)) == hash(value)


class DefaultFactoryTest:
""" Tests for the DefaultFactory class. """
Expand Down Expand Up @@ -167,6 +173,11 @@ def test_default_factory_non_equality(first, second):
assert first != second
assert second != first

@staticmethod
def test_default_factory_hashable():
""" Test hashability (__hash__) of DefaultFactory objects. """
assert hash(DefaultFactory(list)) == hash(list)


class DefaultUnsetTest:
""" Tests for the DefaultUnset sentinel object. """
Expand Down Expand Up @@ -238,6 +249,11 @@ def test_no_default_non_equality(other):
assert NoDefault != other
assert other != NoDefault

@staticmethod
def test_no_default_hashable():
""" Test that NoDefault is hashable (i.e. implements __hash__). """
assert hash(NoDefault) == object.__hash__(NoDefault)

@staticmethod
def test_no_default_call():
""" Test that calling NoDefault returns the sentinel itself. """
Expand Down
4 changes: 2 additions & 2 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[tox]
envlist = clean,py{310,39,38,37},flake8,report
envlist = clean,py{311,310,39,38,37},flake8,report
skip_missing_interpreters = true
isolated_build = true

Expand Down Expand Up @@ -36,7 +36,7 @@ commands =

# These environments basically are an alias for "report" that allow to specify the python version used for coverage.
# tox 4 apparently will have a "labels" option that can be used to define aliases, but that version is not released yet.
[testenv:py{310,39,38,37}-report]
[testenv:py{311,310,39,38,37}-report]
skip_install = true
deps = {[testenv:report]deps}
commands = {[testenv:report]commands}