Repository: openforcefield/openff-toolkit

Issue:
`Molecule.__repr__` effectively hangs for large molecules
**Describe the bug**
<!-- A clear and concise description of what the bug is. -->

The [implementation](https://github.com/openforcefield/openff-toolkit/blob/74cfe94e8cda29e933866b8d895849073ba00689/openff/toolkit/topology/molecule.py#L1319-L1328) of `repr` for the `Molecule` class effectively hangs for large molecules because of (at least) an attempt to convert it to SMILES which waits for a returned value before doing any error handling. The [Python docs for this record](https://docs.python.org/3/library/functions.html#repr) more or less say that it should return something that can re-create the object if passed to `eval` or summary info if that's not feasible. I suggest that for larger molecules (somewhere around 100-1000 atoms) there be no attempt to convert into SMILES, which isn't a particularly useful representation of macromolecules. 

**To Reproduce**
<!-- Steps to reproduce the behavior. A minimal reproducing set of python commands is ideal.

If the problem involves a specific molecule or file, please upload that as well. -->

Create a large `Molecule` from any source like a PDB file or external tool

```python
In [24]: seq = "MNRHLCVWLFRHPSLNGYLQCHIQLHSHQFRQIHLDTRLQVFRQNRNCILHLLSKNWSRRYCHQDTKMLWKHKALQKYMENLSKEYQTLEQCLQHIPVNEENRRSLNRRHA
    ...: ELAPLAAIYQEIQETEQAIEELESMCKSLNKQDEKQLQELALEERQTIDQKINMLYNELFQSLVPKEKYDKNDVILEVTAGRTTGGDICQQFTREIFDMYQNYSCYKHWQFELLNYTP
    ...: ADYGGLHHAAARISGDGVYKHLKYEGGIHRVQRIPEVGLSSRMQRIHTGTMSVIVLPQPDEVDVKLDPKDLRIDTFRAKGAGGQHVNKTDSAVRLVHIPTGLVVECQQERSQIKNKEI
    ...: AFRVLRARLYQQIIEKDKRQQQSARKLQVGTRAQSERIRTYNFTQDRVSDHRIAYEVRDIKEFLCGGKGLDQLIQRLLQSADEEAIAELLDEHLKSAK"

In [25]: Molecule.from_rdkit(Chem.MolFromSequence(seq), allow_undefined_stereo=True)
```

**Output**
<!-- The full error message (may be large, that's fine. Better to paste too much than too little.) -->

Basically stalls; if I didn't know this codebase better I'd be confused and/or think that the molecule loading failed.

Discussion:
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/openforcefield/openff-toolkit/pull/2026?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=openforcefield) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 82.58%. Comparing base [(`21972ae`)](https://app.codecov.io/gh/openforcefield/openff-toolkit/commit/21972ae6668ae72a879f43fa8c383fd4ac5c877d?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=openforcefield) to head [(`fbe6299`)](https://app.codecov.io/gh/openforcefield/openff-toolkit/commit/fbe62991ef37b0f1a8e418d3e110b32470d14550?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=openforcefield).

> :exclamation:  There is a different number of reports uploaded between BASE (21972ae) and HEAD (fbe6299). Click for more details.
> 
> <details><summary>HEAD has 53 uploads less than BASE</summary>
>
>| Flag | BASE (21972ae) | HEAD (fbe6299) |
>|------|------|------|
>||60|7|
></details>

<details><summary>Additional details and impacted files</summary>


</details>
mattwthompson: Thanks! This has been a (tiny) thorn in my side for a while

Patch:
diff --git a/docs/releasehistory.md b/docs/releasehistory.md
index 2c7a6ac8..30c1a123 100644
--- a/docs/releasehistory.md
+++ b/docs/releasehistory.md
@@ -12,6 +12,8 @@ Releases follow the `major.minor.micro` scheme recommended by [PEP440](https://w
 
 ### Behavior changes
 
+- [PR #2026](https://github.com/openforcefield/openff-toolkit/pull/2026): Makes `Molecule.__repr__` more succinct for large molecules.
+
 ### Bugfixes
 
 ### New features
diff --git a/openff/toolkit/topology/molecule.py b/openff/toolkit/topology/molecule.py
index 149c19dc..ce53438c 100644
--- a/openff/toolkit/topology/molecule.py
+++ b/openff/toolkit/topology/molecule.py
@@ -1354,15 +1354,20 @@ class FrozenMolecule(Serializable):
                     tuple(element_dict["identifier"]), element_dict["atom_indices"]
                 )
 
-    def __repr__(self):
-        """Return a summary of this molecule; SMILES if valid, Hill formula if not."""
+    def __repr__(self) -> str:
+        """Return a summary of this molecule; SMILES if valid, Hill formula if not or if large."""
         description = f"Molecule with name '{self.name}'"
+
+        if self.n_atoms > 500:
+            hill = self.to_hill_formula()
+            return description + f" with Hill formula '{hill}'"
+
         try:
             smiles = self.to_smiles()
+            return description + f" and SMILES '{smiles}'"
         except Exception:
             hill = self.to_hill_formula()
             return description + f" with bad SMILES and Hill formula '{hill}'"
-        return description + f" and SMILES '{smiles}'"
 
     def _initialize(self):
         """


Test patch:
diff --git a/openff/toolkit/_tests/test_molecule.py b/openff/toolkit/_tests/test_molecule.py
index 7d60809b..011b9c11 100644
--- a/openff/toolkit/_tests/test_molecule.py
+++ b/openff/toolkit/_tests/test_molecule.py
@@ -629,6 +629,18 @@ class TestMolecule:
         expected_repr = "Molecule with name '' with bad SMILES and Hill formula 'Cl3'"
         assert molecule.__repr__() == expected_repr
 
+    def test_repr_large_molecule(self):
+        large_molecule = Molecule.from_smiles("CNC" * 100)
+
+        this_repr = repr(large_molecule)
+
+        assert "Hill formula" in this_repr
+        assert "SMILES" not in this_repr
+        assert "C200H502N100" in this_repr
+
+        # can be displayed on one line
+        assert len(this_repr) < 100
+
     @pytest.mark.parametrize("toolkit", [OpenEyeToolkitWrapper, RDKitToolkitWrapper])
     @pytest.mark.parametrize("molecule", mini_drug_bank())
     def test_to_from_smiles(self, molecule, toolkit):