Repository: Materials-Consortia/optimade-python-tools
Pull request #1428 (2022-12-06 23:35:34)

Allow empty strings through chemical formula regexp
Last PR of the day, and one that can actually be reviewed and merged.

After discussions in https://github.com/Materials-Consortia/OPTIMADE/issues/388, it seems that empty formulae should be allowed -- though we should wait until it is really resolved to merge this.

This PR adjusted the regexp for this and moves the test case around.

This has knock-on effects on open PRs in other repos (schemas, specification) that will need to be untangled at a later date.

codecov[bot]: # [Codecov](https://codecov.io/gh/Materials-Consortia/optimade-python-tools/pull/1428?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Materials-Consortia) Report
> Merging [#1428](https://codecov.io/gh/Materials-Consortia/optimade-python-tools/pull/1428?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Materials-Consortia) (4c18c23) into [master](https://codecov.io/gh/Materials-Consortia/optimade-python-tools/commit/a8c84c5f193e668eb8b5fc3f0db1d6a63796f472?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Materials-Consortia) (a8c84c5) will **not change** coverage.
> The diff coverage is `100.00%`.

```diff
@@           Coverage Diff           @@
##           master    #1428   +/-   ##
=======================================
  Coverage   91.43%   91.43%           
=======================================
  Files          74       74           
  Lines        4392     4392           
=======================================
  Hits         4016     4016           
  Misses        376      376           
```

| Flag | Coverage Δ | |
|---|---|---|
| project | `91.43% <100.00%> (ø)` | |
| validator | `91.53% <100.00%> (ø)` | |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Materials-Consortia#carryforward-flags-in-the-pull-request-comment) to find out more.

| [Impacted Files](https://codecov.io/gh/Materials-Consortia/optimade-python-tools/pull/1428?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Materials-Consortia) | Coverage Δ | |
|---|---|---|
| [optimade/models/utils.py](https://codecov.io/gh/Materials-Consortia/optimade-python-tools/pull/1428/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Materials-Consortia#diff-b3B0aW1hZGUvbW9kZWxzL3V0aWxzLnB5) | `92.78% <100.00%> (ø)` | |

Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Materials-Consortia). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Materials-Consortia)


Patch:
diff --git a/openapi/openapi.json b/openapi/openapi.json
index 918a2965..180dce2b 100644
--- a/openapi/openapi.json
+++ b/openapi/openapi.json
@@ -3360,8 +3360,8 @@
             },
             "description": "If present MUST be a list of floats expressed in a.m.u.\nElements denoting vacancies MUST have masses equal to 0.",
             "x-optimade-queryable": "optional",
-            "x-optimade-support": "optional",
-            "x-optimade-unit": "a.m.u."
+            "x-optimade-unit": "a.m.u.",
+            "x-optimade-support": "optional"
           },
           "original_name": {
             "title": "Original Name",
@@ -3579,7 +3579,7 @@
           },
           "chemical_formula_reduced": {
             "title": "Chemical Formula Reduced",
-            "pattern": "^([A-Z][a-z]?([2-9]|[1-9]\\d+)?)+$",
+            "pattern": "(^$)|^([A-Z][a-z]?([2-9]|[1-9]\\d+)?)+$",
             "type": "string",
             "description": "The reduced chemical formula for a structure as a string with element symbols and integer chemical proportion numbers.\nThe proportion number MUST be omitted if it is 1.\n\n- **Type**: string\n\n- **Requirements/Conventions**:\n    - **Support**: SHOULD be supported by all implementations, i.e., SHOULD NOT be `null`.\n    - **Query**: MUST be a queryable property.\n      However, support for filters using partial string matching with this property is OPTIONAL (i.e., BEGINS WITH, ENDS WITH, and CONTAINS).\n      Intricate queries on formula components are instead suggested to be formulated using set-type filter operators on the multi valued `elements` and `elements_ratios` properties.\n    - Element symbols MUST have proper capitalization (e.g., `\"Si\"`, not `\"SI\"` for \"silicon\").\n    - Elements MUST be placed in alphabetical order, followed by their integer chemical proportion number.\n    - For structures with no partial occupation, the chemical proportion numbers are the smallest integers for which the chemical proportion is exactly correct.\n    - For structures with partial occupation, the chemical proportion numbers are integers that within reasonable approximation indicate the correct chemical proportions. The precise details of how to perform the rounding is chosen by the API implementation.\n    - No spaces or separators are allowed.\n\n- **Examples**:\n    - `\"H2NaO\"`\n    - `\"ClNa\"`\n    - `\"CCaO3\"`\n\n- **Query examples**:\n    - A filter that matches an exactly given formula is `chemical_formula_reduced=\"H2NaO\"`.",
             "nullable": true,
@@ -3588,7 +3588,7 @@
           },
           "chemical_formula_hill": {
             "title": "Chemical Formula Hill",
-            "pattern": "^([A-Z][a-z]?([2-9]|[1-9]\\d+)?)+$",
+            "pattern": "(^$)|^([A-Z][a-z]?([2-9]|[1-9]\\d+)?)+$",
             "type": "string",
             "description": "The chemical formula for a structure in [Hill form](https://dx.doi.org/10.1021/ja02046a005) with element symbols followed by integer chemical proportion numbers. The proportion number MUST be omitted if it is 1.\n\n- **Type**: string\n\n- **Requirements/Conventions**:\n    - **Support**: OPTIONAL support in implementations, i.e., MAY be `null`.\n    - **Query**: Support for queries on this property is OPTIONAL.\n      If supported, only a subset of the filter features MAY be supported.\n    - The overall scale factor of the chemical proportions is chosen such that the resulting values are integers that indicate the most chemically relevant unit of which the system is composed.\n      For example, if the structure is a repeating unit cell with four hydrogens and four oxygens that represents two hydroperoxide molecules, `chemical_formula_hill` is `\"H2O2\"` (i.e., not `\"HO\"`, nor `\"H4O4\"`).\n    - If the chemical insight needed to ascribe a Hill formula to the system is not present, the property MUST be handled as unset.\n    - Element symbols MUST have proper capitalization (e.g., `\"Si\"`, not `\"SI\"` for \"silicon\").\n    - Elements MUST be placed in [Hill order](https://dx.doi.org/10.1021/ja02046a005), followed by their integer chemical proportion number.\n      Hill order means: if carbon is present, it is placed first, and if also present, hydrogen is placed second.\n      After that, all other elements are ordered alphabetically.\n      If carbon is not present, all elements are ordered alphabetically.\n    - If the system has sites with partial occupation and the total occupations of each element do not all sum up to integers, then the Hill formula SHOULD be handled as unset.\n    - No spaces or separators are allowed.\n\n- **Examples**:\n    - `\"H2O2\"`\n\n- **Query examples**:\n    - A filter that matches an exactly given formula is `chemical_formula_hill=\"H2O2\"`.",
             "x-optimade-queryable": "optional",
@@ -3596,7 +3596,7 @@
           },
           "chemical_formula_anonymous": {
             "title": "Chemical Formula Anonymous",
-            "pattern": "^([A-Z][a-z]?([2-9]|[1-9]\\d+)?)+$",
+            "pattern": "(^$)|^([A-Z][a-z]?([2-9]|[1-9]\\d+)?)+$",
             "type": "string",
             "description": "The anonymous formula is the `chemical_formula_reduced`, but where the elements are instead first ordered by their chemical proportion number, and then, in order left to right, replaced by anonymous symbols A, B, C, ..., Z, Aa, Ba, ..., Za, Ab, Bb, ... and so on.\n\n- **Type**: string\n\n- **Requirements/Conventions**:\n    - **Support**: SHOULD be supported by all implementations, i.e., SHOULD NOT be `null`.\n    - **Query**: MUST be a queryable property.\n      However, support for filters using partial string matching with this property is OPTIONAL (i.e., BEGINS WITH, ENDS WITH, and CONTAINS).\n\n- **Examples**:\n    - `\"A2B\"`\n    - `\"A42B42C16D12E10F9G5\"`\n\n- **Querying**:\n    - A filter that matches an exactly given formula is `chemical_formula_anonymous=\"A2B\"`.",
             "nullable": true,
@@ -3640,8 +3640,8 @@
             "description": "The three lattice vectors in Cartesian coordinates, in \u00e5ngstr\u00f6m (\u00c5).\n\n- **Type**: list of list of floats or unknown values.\n\n- **Requirements/Conventions**:\n    - **Support**: SHOULD be supported by all implementations, i.e., SHOULD NOT be `null`.\n    - **Query**: Support for queries on this property is OPTIONAL.\n      If supported, filters MAY support only a subset of comparison operators.\n    - MUST be a list of three vectors *a*, *b*, and *c*, where each of the vectors MUST BE a list of the vector's coordinates along the x, y, and z Cartesian coordinates.\n      (Therefore, the first index runs over the three lattice vectors and the second index runs over the x, y, z Cartesian coordinates).\n    - For databases that do not define an absolute Cartesian system (e.g., only defining the length and angles between vectors), the first lattice vector SHOULD be set along *x* and the second on the *xy*-plane.\n    - MUST always contain three vectors of three coordinates each, independently of the elements of property `dimension_types`.\n      The vectors SHOULD by convention be chosen so the determinant of the `lattice_vectors` matrix is different from zero.\n      The vectors in the non-periodic directions have no significance beyond fulfilling these requirements.\n    - The coordinates of the lattice vectors of non-periodic dimensions (i.e., those dimensions for which `dimension_types` is `0`) MAY be given as a list of all `null` values.\n        If a lattice vector contains the value `null`, all coordinates of that lattice vector MUST be `null`.\n\n- **Examples**:\n    - `[[4.0,0.0,0.0],[0.0,4.0,0.0],[0.0,1.0,4.0]]` represents a cell, where the first vector is `(4, 0, 0)`, i.e., a vector aligned along the `x` axis of length 4 \u00c5; the second vector is `(0, 4, 0)`; and the third vector is `(0, 1, 4)`.",
             "nullable": true,
             "x-optimade-queryable": "optional",
-            "x-optimade-support": "should",
-            "x-optimade-unit": "\u00c5"
+            "x-optimade-unit": "\u00c5",
+            "x-optimade-support": "should"
           },
           "cartesian_site_positions": {
             "title": "Cartesian Site Positions",
@@ -3657,8 +3657,8 @@
             "description": "Cartesian positions of each site in the structure.\nA site is usually used to describe positions of atoms; what atoms can be encountered at a given site is conveyed by the `species_at_sites` property, and the species themselves are described in the `species` property.\n\n- **Type**: list of list of floats\n\n- **Requirements/Conventions**:\n    - **Support**: SHOULD be supported by all implementations, i.e., SHOULD NOT be `null`.\n    - **Query**: Support for queries on this property is OPTIONAL.\n      If supported, filters MAY support only a subset of comparison operators.\n    - It MUST be a list of length equal to the number of sites in the structure, where every element is a list of the three Cartesian coordinates of a site expressed as float values in the unit angstrom (\u00c5).\n    - An entry MAY have multiple sites at the same Cartesian position (for a relevant use of this, see e.g., the property `assemblies`).\n\n- **Examples**:\n    - `[[0,0,0],[0,0,2]]` indicates a structure with two sites, one sitting at the origin and one along the (positive) *z*-axis, 2 \u00c5 away from the origin.",
             "nullable": true,
             "x-optimade-queryable": "optional",
-            "x-optimade-support": "should",
-            "x-optimade-unit": "\u00c5"
+            "x-optimade-unit": "\u00c5",
+            "x-optimade-support": "should"
           },
           "nsites": {
             "title": "Nsites",
diff --git a/optimade/models/utils.py b/optimade/models/utils.py
index 603ef08a..0474cdf9 100644
--- a/optimade/models/utils.py
+++ b/optimade/models/utils.py
@@ -231,7 +231,7 @@ def anonymous_element_generator():
 ANONYMOUS_ELEMENTS = tuple(itertools.islice(anonymous_element_generator(), 150))
 """ Returns the first 150 values of the anonymous element generator. """
 
-CHEMICAL_FORMULA_REGEXP = r"^([A-Z][a-z]?([2-9]|[1-9]\d+)?)+$"
+CHEMICAL_FORMULA_REGEXP = r"(^$)|^([A-Z][a-z]?([2-9]|[1-9]\d+)?)+$"
 
 EXTRA_SYMBOLS = ["X", "vacancy"]
 


Test patch:
diff --git a/tests/models/test_utils.py b/tests/models/test_utils.py
index 955ecdee..bfeb3a1b 100644
--- a/tests/models/test_utils.py
+++ b/tests/models/test_utils.py
@@ -96,6 +96,7 @@ def test_formula_regexp():
         "LiP5",
         "Jn7Qb4",  # Regexp does not care about the actual existence of elements
         "A5B213CeD3E65F12G",
+        "",
     )
 
     bad_formulae = (
@@ -106,7 +107,7 @@ def test_formula_regexp():
         "6F7G",
         "A0Be2",
         "A1Be2",
-        "",
+        "A0B1",
     )
 
     for formula in good_formulae: