Repository: glotzerlab/signac (python)

Issue:
project.detect_schema() should have a pretty representation in IPython
### Feature description
In #127, we added an IPython representation for `JobsCursor` objects. I would like the same kind of behavior for `ProjectSchema` objects, with a pretty format that looks similar to the output of `signac schema`.

### Proposed solution

Add a method `_repr_html_` to `ProjectSchema`. Refer to `JobsCursor` and `Project` classes for examples.

Pull request description:
Added pretty representation of ProjectSchema for IPython
<!-- Provide a general summary of your changes in the Title above -->

## Description
<!-- Describe your changes in detail -->
Added `_repr_html_` method in  `ProjectSchema`
## Motivation and Context
Resolves #314 
<!-- Why is this change required? What problem does it solve? -->
<!-- If it fixes an open issue, please link to the issue here. -->

## 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.
- [x] I have updated the [changelog](https://github.com/glotzerlab/signac/blob/master/changelog.txt) and added all related issue and pull request numbers for future reference (if applicable). See example below.


Example for a changelog entry: `Fix issue with launching rockets to the moon (#101, #212).`


Patch:
diff --git a/changelog.txt b/changelog.txt
index 4753e329..91e497f3 100644
--- a/changelog.txt
+++ b/changelog.txt
@@ -14,6 +14,7 @@ Added
 +++++
 
  - Type annotations are validated during continuous integration (#313).
+ - Added ``_repr_html_`` method in ``ProjectSchema`` class (#314, #324).
 
 Fixed
 +++++
diff --git a/signac/contrib/schema.py b/signac/contrib/schema.py
index ced941d7..1e817ec3 100644
--- a/signac/contrib/schema.py
+++ b/signac/contrib/schema.py
@@ -136,6 +136,12 @@ class ProjectSchema(object):
     def __repr__(self):
         return "{}(<len={}>)".format(type(self).__name__, len(self))
 
+    def _repr_html_(self):
+        import html
+        output = "<strong>" + html.escape(repr(self)) + "</strong>"
+        output += "<pre>" + str(self) + "</pre>"
+        return output
+
     def __contains__(self, key_or_keys):
         if isinstance(key_or_keys, str):
             return key_or_keys in self._schema


Test patch:
diff --git a/tests/test_project.py b/tests/test_project.py
index 2f36b558..a128dfd9 100644
--- a/tests/test_project.py
+++ b/tests/test_project.py
@@ -1503,6 +1503,15 @@ class TestProjectRepresentation(TestProjectBase):
                                     raise pytest.skip('requires use_pandas')
                                 self.project.find_jobs(filter_)._repr_html_()
 
+        with subtests.test(of='Schema'):
+            schema = self.project.detect_schema()
+            with subtests.test(type='str'):
+                str(schema)
+            with subtests.test(type='repr'):
+                repr(schema)
+            with subtests.test(type='html'):
+                schema._repr_html_()
+
     def test_repr_no_jobs(self, subtests):
         self.call_repr_methods(subtests)