Repository: railwayapp/nixpacks
Pull request #319 (2022-07-15 19:26:26)

Fix python numpy and pandas
## What does this PR address?
- Add the `zlib` and `stdenv.cc.cc.lib` Nix packages as libraries if numpy is detected
- Docker test for build and running python projects that use numpy and pandas



Patch:
diff --git a/examples/python-numpy/main.py b/examples/python-numpy/main.py
new file mode 100644
index 0000000..91cf460
--- /dev/null
+++ b/examples/python-numpy/main.py
@@ -0,0 +1,10 @@
+import numpy as np
+import pandas as pd
+
+print(np)
+print(pd)
+
+arr = np.random.rand(2, 3)
+print(arr)
+
+print("Hello from Python numpy and pandas")
diff --git a/examples/python-numpy/requirements.txt b/examples/python-numpy/requirements.txt
new file mode 100644
index 0000000..1341486
--- /dev/null
+++ b/examples/python-numpy/requirements.txt
@@ -0,0 +1,2 @@
+numpy==1.23.1
+pandas==1.4.3
diff --git a/src/providers/python.rs b/src/providers/python.rs
index 0e21636..279d3ea 100644
--- a/src/providers/python.rs
+++ b/src/providers/python.rs
@@ -47,7 +47,15 @@ impl Provider for PythonProvider {
             pkgs.append(&mut vec![Pkg::new("postgresql"), Pkg::new("gcc")]);
         }
 
-        Ok(Some(SetupPhase::new(pkgs)))
+        let mut setup_phase = SetupPhase::new(pkgs);
+
+        // Numpy needs some C headers to be available
+        // stdenv.cc.cc.lib -> https://discourse.nixos.org/t/nixos-with-poetry-installed-pandas-libstdc-so-6-cannot-open-shared-object-file/8442/3
+        if PythonProvider::uses_numpy(app)? {
+            setup_phase.add_libraries(vec!["zlib".to_string(), "stdenv.cc.cc.lib".to_string()]);
+        }
+
+        Ok(Some(setup_phase))
     }
 
     fn install(&self, app: &App, _env: &Environment) -> Result<Option<InstallPhase>> {
@@ -305,6 +313,22 @@ impl PythonProvider {
                 .ok_or_else(|| anyhow::anyhow!("failed to load pyproject.toml"))?),
         ))
     }
+
+    fn uses_numpy(app: &App) -> Result<bool> {
+        let requirements_numpy = app.includes_file("requirements.txt")
+            && app
+                .read_file("requirements.txt")?
+                .to_lowercase()
+                .contains("numpy");
+
+        let project_numpy = app.includes_file("pyproject.toml")
+            && app
+                .read_file("pyproject.toml")?
+                .to_lowercase()
+                .contains("numpy");
+
+        Ok(requirements_numpy || project_numpy)
+    }
 }
 
 #[cfg(test)]
@@ -354,4 +378,17 @@ mod test {
 
         Ok(())
     }
+
+    #[test]
+    fn test_numpy_detection() -> Result<()> {
+        assert_eq!(
+            PythonProvider::uses_numpy(&App::new("./examples/python")?)?,
+            false
+        );
+        assert_eq!(
+            PythonProvider::uses_numpy(&App::new("./examples/python-numpy")?)?,
+            true
+        );
+        Ok(())
+    }
 }


Test patch:
diff --git a/tests/docker_run_tests.rs b/tests/docker_run_tests.rs
index 23a10be..a205fd3 100644
--- a/tests/docker_run_tests.rs
+++ b/tests/docker_run_tests.rs
@@ -358,6 +358,13 @@ fn test_python_poetry() {
     assert!(output.contains("Hello from Python-Poetry"));
 }
 
+#[test]
+fn test_python_numpy() {
+    let name = simple_build("./examples/python-numpy");
+    let output = run_image(name, None);
+    assert!(output.contains("Hello from Python numpy and pandas"));
+}
+
 #[test]
 fn test_rust_custom_version() {
     let name = Uuid::new_v4().to_string();