Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle non-Job objects in Job equality check. #455

Merged
merged 8 commits into from
Jan 11, 2021
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
2 changes: 1 addition & 1 deletion changelog.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Added
Changed
+++++++

- Optimized job hash and equality checks (#442).
- Optimized job hash and equality checks (#442, #455).
- Optimized ``H5Store`` initialization (#443).
- State points are loaded lazily when ``Job`` is opened by id (#238, #239).

Expand Down
8 changes: 5 additions & 3 deletions signac/contrib/job.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,9 +151,11 @@ def __hash__(self):
return hash(self.id)

def __eq__(self, other):
return (self.id == other.id) and (
os.path.realpath(self._wd) == os.path.realpath(other._wd)
)
if not isinstance(other, type(self)):
return NotImplemented
return self.id == other.id and os.path.realpath(
self.workspace()
) == os.path.realpath(other.workspace())

def __str__(self):
"""Return the job's id."""
Expand Down
33 changes: 33 additions & 0 deletions tests/test_job.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,39 @@ def test_str(self):
job = self.project.open_job({"a": 0})
assert str(job) == job.id

def test_eq(self):
job = self.project.open_job({"a": 0})

# Make sure that Jobs can only be equal to other Job instances.
class NonJob:
"""Minimal class that cannot be compared with Job objects."""

def __init__(self, job):
self.id = job.id
self._workspace = job.workspace()

class JobSubclass(Job):
"""Minimal subclass that can be compared with Job objects."""

def __init__(self, job):
self._id = job.id
self._workspace = job.workspace()

def workspace(self):
return self._workspace

non_job = NonJob(job)
assert job != non_job
assert non_job != job
Comment on lines +155 to +157
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why does this ensure that we have NotImplemeted as opposed to False?

Copy link
Member Author

@bdice bdice Jan 3, 2021

Choose a reason for hiding this comment

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

I'm testing both a != b and b != a because of the order in which Python resolves __eq__ operators. This section of the Python docs explains why both tests are relevant here, and why we return NotImplemented here: object.__eq__ docs.

Specifically, the order of resolution is controlled by this rule: "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."

Copy link
Member Author

Choose a reason for hiding this comment

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

Also, I used this issue as a reference implementation because it appeared to correct the equality behavior that I was unsure about for subclasses (JobSubclass) and objects of different types with similar APIs (NonJob): googleapis/google-cloud-python#3455


sub_job = JobSubclass(job)
assert job == sub_job
assert sub_job == job

job2 = self.project.open_job({"a": 0})
assert job == job2
assert job2 == job

def test_isfile(self):
job = self.project.open_job({"a": 0})
fn = "test.txt"
Expand Down