Repository: glotzerlab/signac
Pull request #250 (2019-12-02 19:01:35)

Add id property for jobs
<!-- Provide a general summary of your changes in the Title above -->
Add `id` property to `Job`

## Description
<!-- Describe your changes in detail -->
Adds `id` as a property of the `Job` class. I also added a test to ensure that it produces the correct string.

## Motivation and Context
<!-- Why is this change required? What problem does it solve? -->
<!-- If it fixes an open issue, please link to the issue here. -->
Just follows the python trend of using properties instead of getters and setters.

## Types of Changes
<!-- Please select all items that apply either now or after creating the pull request: -->
- [ ] Documentation update
- [ ] Bug fix
- [x] New feature
- [ ] Breaking change<sup>1</sup>

<sup>1</sup>The change breaks (or has the potential to break) existing functionality.

## Checklist:
<!-- Please select all items that apply either now or after creating the pull request. -->
<!-- If you are unsure about any of these items, do not hesitate to ask! -->
- [x] I am familiar with the [**Contributing Guidelines**](https://github.com/glotzerlab/signac/blob/master/CONTRIBUTING.md).
- [x] I agree with the terms of the [**Contributor Agreement**](https://github.com/glotzerlab/signac/blob/master/ContributorAgreement.md).
- [x] My name is on the [list of contributors](https://github.com/glotzerlab/signac/blob/master/contributors.yaml).
- [x] My code follows the [code style guideline](https://github.com/glotzerlab/signac/blob/master/CONTRIBUTING.md#code-style) of this project.
- [x] The changes introduced by this pull request are covered by existing or newly introduced tests.

If necessary:
- [ ] I have updated the API documentation as part of the package doc-strings.
- [ ] I have created a separate pull request to update the [framework documentation](https://docs.signac.io/) on [signac-docs](https://github.com/glotzerlab/signac-docs) and linked it here.
- [ ] I have updated the [changelog](https://github.com/glotzerlab/signac/blob/master/changelog.txt).


vyasr: @b-butler I would just return `_id` instead. 
bdice: FYI, `id` is a function in the global namespace in Python. I think it's ok to have this here because it's under the class namespace but just want to avoid shadowing that by accident. The recommendation is to use `id_` instead of `id` where needed.
codecov[bot]: # [Codecov](https://codecov.io/gh/glotzerlab/signac/pull/250?src=pr&el=h1) Report
> Merging [#250](https://codecov.io/gh/glotzerlab/signac/pull/250?src=pr&el=desc) into [master](https://codecov.io/gh/glotzerlab/signac/commit/38a31f96c91b8ebc3dc7674935c24b7ec0547bce?src=pr&el=desc) will **increase** coverage by `0.01%`.
> The diff coverage is `100%`.

[![Impacted file tree graph](https://codecov.io/gh/glotzerlab/signac/pull/250/graphs/tree.svg?width=650&token=FghbjTyzgM&height=150&src=pr)](https://codecov.io/gh/glotzerlab/signac/pull/250?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #250      +/-   ##
==========================================
+ Coverage   64.96%   64.97%   +0.01%     
==========================================
  Files          39       39              
  Lines        5580     5582       +2     
==========================================
+ Hits         3625     3627       +2     
  Misses       1955     1955
```


| [Impacted Files](https://codecov.io/gh/glotzerlab/signac/pull/250?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [signac/contrib/job.py](https://codecov.io/gh/glotzerlab/signac/pull/250/diff?src=pr&el=tree#diff-c2lnbmFjL2NvbnRyaWIvam9iLnB5) | `90.55% <100%> (+0.08%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/glotzerlab/signac/pull/250?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/glotzerlab/signac/pull/250?src=pr&el=footer). Last update [38a31f9...f3fc7e2](https://codecov.io/gh/glotzerlab/signac/pull/250?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

csadorf: So should we deprecate `get_id()` then? The reason that I used `get_id` over `id` was exactly what @bdice mentioned: `id` is a keyword in Python.
b-butler: I think that as long as the keyword is in a class namespace, there isn't any confusion. It isn't a free `id`. There is always an object context associated with it.
vyasr: Fwiw HOOMD (which has a substantially longer history and may have more empirical evidence to base this decision on) runs into the same problem with `hoomd.group.all` (see [this issue](https://github.com/glotzerlab/hoomd-blue/issues/488)).
bdice: Keywords definitely [don't work](https://stackoverflow.com/questions/9746838/why-cant-attribute-names-be-python-keywords). But `id` isn't a keyword. I know HOOMD has had issues with `all` but I don't fully understand why. I commented on the other issue to ask.

Keywords:
```python
>>> import keyword
>>> keyword.kwlist
['False', 'None', 'True', 'and', 'as', 'assert', 'async', 'await', 'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except', 'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is', 'lambda', 'nonlocal', 'not', 'or', 'pass', 'raise', 'return', 'try', 'while', 'with', 'yield']
```
bdice: Related: https://stackoverflow.com/questions/6350847/is-id-a-keyword-in-python
b-butler: @bdice good catch. Regardless having it as an attribute of an object shouldn't raise any confusion.
b-butler: Should we also still raise a `LookupError` when accessing the `project.id` property? It might be confusing to raise an error when trying to access an attribute. I would think returning `None` might be more appropriate.
vyasr: I believe the primary issue in hoomd is simply that groups are needed for many methods, so frequently users assign groups to local variables rather than calling e.g. `hoomd.group.all` everywhere. The documentation and examples implicitly endorse as a best practice based on the prevalence of the idiom `all = hoomd.group.all()` (which is obviously a problem); that's something that we could easily train users out of by removing it from examples, but it's even easier to change the group name. That particular problem is not likely to arise for `job.id`, but maybe there are others.

Regarding the `LookupError`, currently the constructor calls `get_id` as a test of whether the `Project` has been initialized with a valid root directory (one containing the project), so the error is always caught on construction. Since the project config will be immutable going forward, I think it should be safe to raise the error directly in the constructor and then remove that check entirely from `get_id` (which then becomes an alias for the property `project.id`). The only forms of corruption that would be possible are ones that we already don't protect against (like someone manually modifying or removing the config file after the `Project` class instance is created.

Definitely agree, deprecate `Project.get_id` as well.
b-butler: @csadorf I didn't realize I hadn't set the flake8 pre-commit hook for signac core.
csadorf: @bdice Please merge if you are satisfied.
b-butler: The failed test is a performance test of `H5Store`. I am not sure how that happened.
bdice: > The failed test is a performance test of `H5Store`. I am not sure how that happened.

Ignore that. We need to fix some of our tests.
csadorf: > > The failed test is a performance test of `H5Store`. I am not sure how that happened.
> 
> Ignore that. We need to fix some of our tests.

I've updated master to skip those tests there. 

However, the benchmark test failed for py37, for whatever reason I can't trigger a re-run of the tests. @bdice can you do that?
bdice: @b-butler After you check on the docs, this will be ready to merge -- go ahead and click the big green button when you're ready. :)

Patch:
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index c35abca4..c521c687 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -1,6 +1,3 @@
-# The maintainers are requested for all changes.
-* @glotzerlab/signac-maintainers
-
-# Additional reviewers requested for source code and documentation changes.
-/signac/ @glotzerlab/signac-pullassigner
-*.rst @glotzerlab/signac-pullassigner
+# Request one committer and one contributor per PR.
+* @glotzerlab/signac-committers
+* @glotzerlab/signac-contributors
diff --git a/contributors.yaml b/contributors.yaml
index b1732bfb..2371267f 100644
--- a/contributors.yaml
+++ b/contributors.yaml
@@ -66,4 +66,9 @@ contributors:
     given-names: Jenny
     affiliation: "Boise State University"
     orcid: "https://orcid.org/0000-0001-9665-5420"
+  -
+    family-names: Butler
+    given-names: Brandon
+    affiliation: "University of Michigan"
+    orcid: "https://orcid.org/0000-0001-7739-7796"
 ...
diff --git a/doc/api.rst b/doc/api.rst
index 9a4cf72b..0352efb7 100644
--- a/doc/api.rst
+++ b/doc/api.rst
@@ -51,6 +51,7 @@ The Project
     Project.groupby
     Project.groupbydoc
     Project.import_from
+    Project.id
     Project.index
     Project.isfile
     Project.min_len_unique_id
@@ -92,6 +93,7 @@ The Job class
     Job.document
     Job.fn
     Job.get_id
+    Job.id
     Job.init
     Job.isfile
     Job.move
diff --git a/signac/contrib/job.py b/signac/contrib/job.py
index 39baa337..f5e6a0ae 100644
--- a/signac/contrib/job.py
+++ b/signac/contrib/job.py
@@ -6,6 +6,7 @@ import errno
 import logging
 import shutil
 from copy import deepcopy
+from deprecation import deprecated
 
 from ..core import json
 from ..core.attrdict import SyncedAttrDict
@@ -15,6 +16,7 @@ from .hashing import calc_id
 from .utility import _mkdir_p
 from .errors import DestinationExistsError, JobsCorruptedError
 from ..sync import sync_jobs
+from ..version import __version__
 
 
 logger = logging.getLogger(__name__)
@@ -72,7 +74,17 @@ class Job(object):
         # Prepare current working directory for context management
         self._cwd = list()
 
+    @deprecated(deprecated_in="1.3", removed_in="2.0", current_version=__version__,
+                details="Use job.id instead.")
     def get_id(self):
+        """The job's statepoint's unique identifier.
+
+        :return: The job id.
+        :rtype: str"""
+        return self._id
+
+    @property
+    def id(self):
         """The unique identifier for the job's statepoint.
 
         :return: The job id.
@@ -84,7 +96,7 @@ class Job(object):
 
     def __str__(self):
         "Returns the job's id."
-        return str(self.get_id())
+        return str(self.id)
 
     def __repr__(self):
         return "{}(project={}, statepoint={})".format(
diff --git a/signac/contrib/project.py b/signac/contrib/project.py
index c8804173..52549afd 100644
--- a/signac/contrib/project.py
+++ b/signac/contrib/project.py
@@ -142,7 +142,11 @@ class Project(object):
         self._config = _ProjectConfig(config)
 
         # Ensure that the project id is configured.
-        self.get_id()
+        if self.id is None:
+            raise LookupError(
+                "Unable to determine project id. "
+                "Please verify that '{}' is a signac project path.".format(
+                    os.path.abspath(self.config.get('project_dir', os.getcwd()))))
 
         # Prepare project document
         self._fn_doc = os.path.join(self._rd, self.FN_DOCUMENT)
@@ -158,7 +162,7 @@ class Project(object):
 
     def __str__(self):
         "Returns the project's id."
-        return str(self.get_id())
+        return str(self.id)
 
     def __repr__(self):
         return "{type}.get_project('{root}')".format(
@@ -167,7 +171,7 @@ class Project(object):
 
     def _repr_html_(self):
         return "<p>" + \
-            '<strong>Project:</strong> {}<br>'.format(self.get_id()) + \
+            '<strong>Project:</strong> {}<br>'.format(self.id) + \
             "<strong>Root:</strong> {}<br>".format(self.root_directory()) + \
             "<strong>Workspace:</strong> {}<br>".format(self.workspace()) + \
             "<strong>Size:</strong> {}".format(len(self)) + \
@@ -214,20 +218,27 @@ class Project(object):
             such as $HOME."""
         return self._wd
 
+    @deprecated(deprecated_in="1.3", removed_in="2.0", current_version=__version__,
+                details="Use project.id instead.")
     def get_id(self):
         """Get the project identifier.
 
         :return: The project id.
         :rtype: str
-        :raises LookupError: If no project id could be determined.
+        """
+        return self.id
+
+    @property
+    def id(self):
+        """Get the project identifier.
+
+        :return: The project id.
+        :rtype: str
         """
         try:
             return str(self.config['project'])
         except KeyError:
-            raise LookupError(
-                "Unable to determine project id ."
-                "Are you sure '{}' is a signac project path?".format(
-                    os.path.abspath(self.config.get('project_dir', os.getcwd()))))
+            return None
 
     def min_len_unique_id(self):
         "Determine the minimum length required for an id to be unique."
@@ -423,7 +434,7 @@ class Project(object):
         :returns: True when the job is initialized for this project.
         :rtype: bool
         """
-        return os.path.exists(os.path.join(self._wd, job.get_id()))
+        return os.path.exists(os.path.join(self._wd, job.id))
 
     @deprecated(deprecated_in="1.3", removed_in="2.0", current_version=__version__)
     def build_job_search_index(self, index, _trust=False):
@@ -675,7 +686,7 @@ class Project(object):
 
         .. code-block:: python
 
-            {project.open_job(sp).get_id(): sp for sp in statepoints}
+            {project.open_job(sp).id: sp for sp in statepoints}
 
         :param statepoints: A list of statepoints.
         :type statepoints: iterable
@@ -1421,7 +1432,7 @@ class Project(object):
             An instance of :class:`.Project`.
         """
         if name is None:
-            name = os.path.join(self.get_id(), str(uuid.uuid4()))
+            name = os.path.join(self.id, str(uuid.uuid4()))
         if dir is None:
             dir = self.workspace()
         _mkdir_p(self.workspace())  # ensure workspace exists
@@ -1467,11 +1478,11 @@ class Project(object):
                 config['workspace_dir'] = workspace
             config.write()
             project = cls.get_project(root=root)
-            assert project.get_id() == str(name)
+            assert project.id == str(name)
             return project
         else:
             try:
-                assert project.get_id() == str(name)
+                assert project.id == str(name)
                 if workspace is not None:
                     assert os.path.realpath(workspace) \
                         == os.path.realpath(project.workspace())


Test patch:
diff --git a/tests/test_job.py b/tests/test_job.py
index 915a3ba4..724a85aa 100644
--- a/tests/test_job.py
+++ b/tests/test_job.py
@@ -132,7 +132,7 @@ class JobTest(BaseJobTest):
 
     def test_str(self):
         job = self.project.open_job({'a': 0})
-        self.assertEqual(str(job), job.get_id())
+        self.assertEqual(str(job), job.id)
 
     def test_isfile(self):
         job = self.project.open_job({'a': 0})
@@ -274,9 +274,9 @@ class JobSPInterfaceTest(BaseJobTest):
 
     def test_interface_job_identity_change(self):
         job = self.open_job({'a': 0})
-        old_id = job.get_id()
+        old_id = job.id
         job.sp.a = 1
-        self.assertNotEqual(old_id, job.get_id())
+        self.assertNotEqual(old_id, job.id)
 
     def test_interface_nested_kws(self):
         with self.assertRaises(InvalidKeyError):
@@ -289,10 +289,10 @@ class JobSPInterfaceTest(BaseJobTest):
     def test_interface_lists(self):
         job = self.open_job({'a': [1, 2, 3]})
         self.assertEqual(job.sp.a, [1, 2, 3])
-        old_id = job.get_id()
+        old_id = job.id
         job.sp.a.append(4)
         self.assertEqual(job.sp.a, [1, 2, 3, 4])
-        self.assertNotEqual(old_id, job.get_id())
+        self.assertNotEqual(old_id, job.id)
 
     def test_interface_reserved_keywords(self):
         job = self.open_job({'with': 0, 'pop': 1})
@@ -358,11 +358,11 @@ class JobSPInterfaceTest(BaseJobTest):
         job_a = self.open_job(dict(a=0))
         job_b = self.open_job(dict(b=0))
         job_a.init()
-        id_a = job_a.get_id()
+        id_a = job_a.id
         job_a.sp = dict(b=0)
         self.assertEqual(job_a.statepoint(), dict(b=0))
         self.assertEqual(job_a, job_b)
-        self.assertNotEqual(job_a.get_id(), id_a)
+        self.assertNotEqual(job_a.id, id_a)
         job_a = self.open_job(dict(a=0))
         # Moving to existing job, no problem while empty:
         self.assertNotEqual(job_a, job_b)
@@ -388,27 +388,27 @@ class JobSPInterfaceTest(BaseJobTest):
 
         for job in self.project:
             obj_id = id(job)
-            id0 = job.get_id()
+            id0 = job.id
             sp0 = job.statepoint()
             self.assertEqual(id(job), obj_id)
             self.assertTrue(job.sp.a > 0)
-            self.assertEqual(job.get_id(), id0)
+            self.assertEqual(job.id, id0)
             self.assertEqual(job.sp, sp0)
 
             job.sp.a = - job.sp.a
             self.assertEqual(id(job), obj_id)
             self.assertTrue(job.sp.a < 0)
-            self.assertNotEqual(job.get_id(), id0)
+            self.assertNotEqual(job.id, id0)
             self.assertNotEqual(job.sp, sp0)
 
             job.sp.a = - job.sp.a
             self.assertEqual(id(job), obj_id)
             self.assertTrue(job.sp.a > 0)
-            self.assertEqual(job.get_id(), id0)
+            self.assertEqual(job.id, id0)
             self.assertEqual(job.sp, sp0)
             job2 = self.project.open_job(id=id0)
             self.assertEqual(job.sp, job2.sp)
-            self.assertEqual(job.get_id(), job2.get_id())
+            self.assertEqual(job.id, job2.id)
 
     def test_valid_sp_key_types(self):
         job = self.open_job(dict(invalid_key=True)).init()
@@ -537,11 +537,11 @@ class JobOpenAndClosingTest(BaseJobTest):
 
     def test_reopen_job(self):
         with self.open_job(test_token) as job:
-            job_id = job.get_id()
+            job_id = job.id
             self.assertEqual(str(job_id), str(job))
 
         with self.open_job(test_token) as job:
-            self.assertEqual(job.get_id(), job_id)
+            self.assertEqual(job.id, job_id)
         job.remove()
 
     def test_close_nonopen_job(self):
@@ -1517,23 +1517,23 @@ class JobOpenDataTest(BaseJobTest):
 
     def test_statepoint_copy(self):
         job = self.open_job(dict(a=test_token, b=test_token)).init()
-        _id = job.get_id()
+        _id = job.id
         sp_copy = copy.copy(job.sp)
         del sp_copy['b']
         self.assertIn('a', job.sp)
         self.assertNotIn('b', job.sp)
         self.assertIn(job, self.project)
-        self.assertNotEqual(job.get_id(), _id)
+        self.assertNotEqual(job.id, _id)
 
     def test_statepoint_deepcopy(self):
         job = self.open_job(dict(a=test_token, b=test_token)).init()
-        _id = job.get_id()
+        _id = job.id
         sp_copy = copy.deepcopy(job.sp)
         del sp_copy['b']
         self.assertIn('a', job.sp)
         self.assertIn('b', job.sp)
         self.assertNotIn(job, self.project)
-        self.assertEqual(job.get_id(), _id)
+        self.assertEqual(job.id, _id)
 
 
 @unittest.skipIf(not H5PY, 'test requires the h5py package')
diff --git a/tests/test_project.py b/tests/test_project.py
index 2870e2d7..1bfa395a 100644
--- a/tests/test_project.py
+++ b/tests/test_project.py
@@ -57,6 +57,10 @@ class ProjectTest(BaseProjectTest):
         self.assertEqual(self.project.get_id(), 'testing_test_project')
         self.assertEqual(str(self.project), self.project.get_id())
 
+    def test_property_id(self):
+        self.assertEqual(self.project.id, 'testing_test_project')
+        self.assertEqual(str(self.project), self.project.id)
+
     def test_repr(self):
         repr(self.project)
         p = eval(repr(self.project))
@@ -64,7 +68,7 @@ class ProjectTest(BaseProjectTest):
         self.assertEqual(p, self.project)
 
     def test_str(self):
-        str(self.project) == self.project.get_id()
+        str(self.project) == self.project.id
 
     def test_root_directory(self):
         self.assertEqual(self._tmp_pr, self.project.root_directory())
@@ -237,10 +241,10 @@ class ProjectTest(BaseProjectTest):
         self.assertEqual(1, len(list(self.project.find_job_ids(doc_filter={'b': 0}))))
         self.assertEqual(0, len(list(self.project.find_job_ids(doc_filter={'b': 5}))))
         for job_id in self.project.find_job_ids():
-            self.assertEqual(self.project.open_job(id=job_id).get_id(), job_id)
+            self.assertEqual(self.project.open_job(id=job_id).id, job_id)
         index = list(self.project.index())
         for job_id in self.project.find_job_ids(index=index):
-            self.assertEqual(self.project.open_job(id=job_id).get_id(), job_id)
+            self.assertEqual(self.project.open_job(id=job_id).id, job_id)
 
     def test_find_jobs(self):
         statepoints = [{'a': i} for i in range(5)]
@@ -359,11 +363,11 @@ class ProjectTest(BaseProjectTest):
         [self.project.open_job(sp).init() for sp in statepoints]
         aid_len = self.project.min_len_unique_id()
         for job in self.project.find_jobs():
-            aid = job.get_id()[:aid_len]
+            aid = job.id[:aid_len]
             self.assertEqual(self.project.open_job(id=aid), job)
         with self.assertRaises(LookupError):
             for job in self.project.find_jobs():
-                self.project.open_job(id=job.get_id()[:aid_len - 1])
+                self.project.open_job(id=job.id[:aid_len - 1])
         with self.assertRaises(KeyError):
             self.project.open_job(id='abc')
 
@@ -378,7 +382,7 @@ class ProjectTest(BaseProjectTest):
         try:
             logging.disable(logging.CRITICAL)
             with self.assertRaises(JobsCorruptedError):
-                self.project.open_job(id=job.get_id()).init()
+                self.project.open_job(id=job.id).init()
         finally:
             logging.disable(logging.NOTSET)
 
@@ -395,7 +399,7 @@ class ProjectTest(BaseProjectTest):
         try:
             logging.disable(logging.CRITICAL)
             with self.assertRaises(JobsCorruptedError):
-                self.project.open_job(id=job.get_id())
+                self.project.open_job(id=job.id)
         finally:
             logging.disable(logging.NOTSET)
 
@@ -493,7 +497,7 @@ class ProjectTest(BaseProjectTest):
         statepoints = [{'a': i} for i in range(5)]
         for sp in statepoints:
             self.project.open_job(sp).document['test'] = True
-        job_ids = set((job.get_id() for job in self.project.find_jobs()))
+        job_ids = set((job.id for job in self.project.find_jobs()))
         docs = list(self.project.index())
         job_ids_cmp = set((doc['_id'] for doc in docs))
         self.assertEqual(job_ids, job_ids_cmp)
@@ -510,7 +514,7 @@ class ProjectTest(BaseProjectTest):
         statepoints = [{'a': i} for i in range(5)]
         for sp in statepoints:
             self.project.open_job(sp).document['test'] = True
-        job_ids = set((job.get_id() for job in self.project.find_jobs()))
+        job_ids = set((job.id for job in self.project.find_jobs()))
         index = dict()
         for doc in self.project.index():
             index[doc['_id']] = doc
@@ -1779,19 +1783,19 @@ class ProjectInitTest(unittest.TestCase):
         with self.assertRaises(LookupError):
             signac.get_project(root=root)
         project = signac.init_project(name='testproject', root=root)
-        self.assertEqual(project.get_id(), 'testproject')
+        self.assertEqual(project.id, 'testproject')
         self.assertEqual(project.workspace(), os.path.join(root, 'workspace'))
         self.assertEqual(project.root_directory(), root)
         project = signac.Project.init_project(name='testproject', root=root)
-        self.assertEqual(project.get_id(), 'testproject')
+        self.assertEqual(project.id, 'testproject')
         self.assertEqual(project.workspace(), os.path.join(root, 'workspace'))
         self.assertEqual(project.root_directory(), root)
         project = signac.get_project(root=root)
-        self.assertEqual(project.get_id(), 'testproject')
+        self.assertEqual(project.id, 'testproject')
         self.assertEqual(project.workspace(), os.path.join(root, 'workspace'))
         self.assertEqual(project.root_directory(), root)
         project = signac.Project.get_project(root=root)
-        self.assertEqual(project.get_id(), 'testproject')
+        self.assertEqual(project.id, 'testproject')
         self.assertEqual(project.workspace(), os.path.join(root, 'workspace'))
         self.assertEqual(project.root_directory(), root)
 
@@ -1826,17 +1830,17 @@ class ProjectInitTest(unittest.TestCase):
         with self.assertRaises(LookupError):
             signac.get_project(root=root)
         project = signac.init_project(name='testproject', root=root)
-        self.assertEqual(project.get_id(), 'testproject')
+        self.assertEqual(project.id, 'testproject')
         self.assertEqual(project.workspace(), os.path.join(root, 'workspace'))
         self.assertEqual(project.root_directory(), root)
         # Second initialization should not make any difference.
         project = signac.init_project(name='testproject', root=root)
         project = signac.get_project(root=root)
-        self.assertEqual(project.get_id(), 'testproject')
+        self.assertEqual(project.id, 'testproject')
         self.assertEqual(project.workspace(), os.path.join(root, 'workspace'))
         self.assertEqual(project.root_directory(), root)
         project = signac.Project.get_project(root=root)
-        self.assertEqual(project.get_id(), 'testproject')
+        self.assertEqual(project.id, 'testproject')
         self.assertEqual(project.workspace(), os.path.join(root, 'workspace'))
         self.assertEqual(project.root_directory(), root)
         # Deviating initialization parameters should result in errors.
@@ -1864,21 +1868,21 @@ class ProjectInitTest(unittest.TestCase):
         root_a = os.path.join(root, 'project_a')
         root_b = os.path.join(root_a, 'project_b')
         signac.init_project('testprojectA', root_a)
-        self.assertEqual(signac.get_project(root=root_a).get_id(), 'testprojectA')
+        self.assertEqual(signac.get_project(root=root_a).id, 'testprojectA')
         check_root(root_a)
         signac.init_project('testprojectB', root_b)
-        self.assertEqual(signac.get_project(root=root_b).get_id(), 'testprojectB')
+        self.assertEqual(signac.get_project(root=root_b).id, 'testprojectB')
         check_root(root_b)
         cwd = os.getcwd()
         try:
             os.chdir(root_a)
             check_root()
-            self.assertEqual(signac.get_project().get_id(), 'testprojectA')
+            self.assertEqual(signac.get_project().id, 'testprojectA')
         finally:
             os.chdir(cwd)
         try:
             os.chdir(root_b)
-            self.assertEqual(signac.get_project().get_id(), 'testprojectB')
+            self.assertEqual(signac.get_project().id, 'testprojectB')
             check_root()
         finally:
             os.chdir(cwd)