Repository: Materials-Consortia/optimade-python-tools (python)
Issue: Lark files not being distributed
The grammar Lark files are not being distributed anymore in the normal installation packages.

This is a crucial bug that needs to be fixed!
Pull request description: Make sure relevant package data is included in distributions
Fixes #141

Bumps version to v0.3.2

Patch:
diff --git a/MANIFEST.in b/MANIFEST.in
index 37b65cb9..4a047b4d 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,3 +1,3 @@
 recursive-include optimade/server/ *.ini *.json
-recursive-include optimade/server/data *
-recursive-include optimade/grammar *.g
+recursive-include optimade/server/data/ *
+recursive-include optimade/grammar/ *.lark
diff --git a/optimade/__init__.py b/optimade/__init__.py
index d7ab00a4..1a6eec74 100644
--- a/optimade/__init__.py
+++ b/optimade/__init__.py
@@ -1,2 +1,2 @@
-__version__ = "0.3.1"
+__version__ = "0.3.2"
 __api_version__ = "0.10.1"
diff --git a/optimade/server/main.py b/optimade/server/main.py
index e0481a79..27107eed 100644
--- a/optimade/server/main.py
+++ b/optimade/server/main.py
@@ -1,3 +1,4 @@
+import os
 import json
 from pathlib import Path
 
@@ -91,12 +92,10 @@ for prefix in valid_prefixes:
 
 def update_schema(app):
     """Update OpenAPI schema in file 'local_openapi.json'"""
-    local_openapi = (
-        Path(__file__)
-        .resolve()
-        .parent.parent.parent.joinpath("openapi/local_openapi.json")
-    )
-    with open(local_openapi, "w") as f:
+    package_root = Path(__file__).parent.parent.parent.resolve()
+    if not package_root.joinpath("openapi").exists():
+        os.mkdir(package_root.joinpath("openapi"))
+    with open(package_root.joinpath("openapi/local_openapi.json"), "w") as f:
         json.dump(app.openapi(), f, indent=2)
 
 
diff --git a/optimade/server/main_index.py b/optimade/server/main_index.py
index 6740cba1..e88ded7b 100644
--- a/optimade/server/main_index.py
+++ b/optimade/server/main_index.py
@@ -1,3 +1,4 @@
+import os
 import json
 from pathlib import Path
 
@@ -74,12 +75,10 @@ for prefix in valid_prefixes:
 
 def update_schema(app):
     """Update OpenAPI schema in file 'local_index_openapi.json'"""
-    local_openapi = (
-        Path(__file__)
-        .resolve()
-        .parent.parent.parent.joinpath("openapi/local_index_openapi.json")
-    )
-    with open(local_openapi, "w") as f:
+    package_root = Path(__file__).parent.parent.parent.resolve()
+    if not package_root.joinpath("openapi").exists():
+        os.mkdir(package_root.joinpath("openapi"))
+    with open(package_root.joinpath("openapi/local_index_openapi.json"), "w") as f:
         json.dump(app.openapi(), f, indent=2)
 
 
diff --git a/setup.py b/setup.py
index c5fd2da8..d6f14da3 100644
--- a/setup.py
+++ b/setup.py
@@ -20,7 +20,7 @@ all_deps = dev_deps + django_deps + elastic_deps
 
 setup(
     name="optimade",
-    version="0.3.1",
+    version="0.3.2",
     url="https://github.com/Materials-Consortia/optimade-python-tools",
     license="MIT",
     author="OPTiMaDe Development Team",


Test patch:
diff --git a/tests/test_setup.py b/tests/test_setup.py
new file mode 100644
index 00000000..ff972b7a
--- /dev/null
+++ b/tests/test_setup.py
@@ -0,0 +1,58 @@
+import os
+from pathlib import Path
+import re
+import tempfile
+import unittest
+
+
+class TestSetup(unittest.TestCase):
+    """Test ./setup.py"""
+
+    def test_distributions_package(self):
+        """Make sure a distribution has all the needed package data"""
+        package_root = Path(__file__).parent.parent.resolve()
+
+        number_of_grammar_files = len(
+            list(package_root.joinpath("optimade/grammar").rglob("*.lark"))
+        )
+
+        with tempfile.TemporaryDirectory() as temp_dir:
+            current_dir = Path(temp_dir)
+            file_output = current_dir.joinpath("sdist.out")
+
+            os.system(
+                f"python {package_root.joinpath('setup.py')} sdist --dry-run > {file_output}"
+            )
+
+            with open(file_output, "r") as file_:
+                # Removing new-line-chars via slice
+                lines = [_[:-1] for _ in file_.readlines()]
+
+            present = {
+                r"\.lark": False,
+                r"config\.ini": False,
+                r"index_links\.json": False,
+                r"test_structures\.json": False,
+                r"test_references\.json": False,
+                r"test_links\.json": False,
+            }
+            count = 0
+            for line in lines:
+                for requirement in present:
+                    if re.findall(requirement, line):
+                        if requirement == r"\.lark":
+                            count += 1
+                            if count == number_of_grammar_files:
+                                present[requirement] = True
+                            else:
+                                present[requirement] = False
+                        else:
+                            present[requirement] = True
+
+            for requirement, is_present in present.items():
+                if requirement == r"\.lark":
+                    msg = f"{requirement} files NOT found. Expected {number_of_grammar_files}, found instead {count}."
+                else:
+                    msg = f"{requirement} file NOT found."
+
+                self.assertTrue(is_present, msg=msg)