Created
May 20, 2025 20:52
-
-
Save ZachWatkins/7afdc8d43900327f54f0a0d19c440a7e to your computer and use it in GitHub Desktop.
Laravel Python Service Class
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Contracts; | |
interface PythonProvider | |
{ | |
public function run(string $filename, array $options): string; | |
public function install(): void; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Services; | |
use App\Contracts\PythonProvider; | |
use Illuminate\Support\Facades\Storage; | |
use Illuminate\Support\Facades\Process; | |
class PythonService implements PythonProvider | |
{ | |
/** | |
* Run a Python script with the given filename and options. | |
* | |
* @param string $filename The name of the Python script to run. | |
* @param array $options The options to pass to the script. | |
* @return string The output of the script. | |
* @throws \InvalidArgumentException If the filename or options are invalid. | |
* @throws \RuntimeException If the script fails to run. | |
*/ | |
public function run(string $filename, array $options): string | |
{ | |
if (app()->environment('testing')) { | |
return "testing"; | |
} | |
// Sanitize the filename and options to prevent command injection. | |
if (!in_array($filename, ['filename.py'])) { | |
throw new \InvalidArgumentException("Invalid filename: {$filename}"); | |
} | |
$options = array_map('escapeshellarg', $options); | |
foreach ($options as $option) { | |
if (preg_match('/[|;&<>]/', $option)) { | |
throw new \InvalidArgumentException("Invalid option: {$option}"); | |
} | |
} | |
$filepath = Storage::path($filename); | |
if (!is_dir(dirname($filepath))) { | |
throw new \InvalidArgumentException("Directory not found: " . dirname($filepath)); | |
} | |
if (!file_exists($filepath)) { | |
throw new \InvalidArgumentException("File not found: {$filename}"); | |
} | |
if (!is_executable($filepath)) { | |
throw new \InvalidArgumentException("File not executable: {$filename}"); | |
} | |
$options = implode(' ', $options); | |
$result = Process::command('python3 ' . $filename . ($options ? ' ' . $options : '')) | |
->timeout(120) | |
->idleTimeout(120) | |
->run(); | |
if (!$result->successful()) { | |
throw new \RuntimeException("Python script failed: " . $result->errorOutput()); | |
} | |
return $result->output(); | |
} | |
/** | |
* Install Python if it is not already installed. | |
* | |
* @throws \RuntimeException If the installation fails. | |
*/ | |
public function install(): void | |
{ | |
if (app()->environment('testing')) { | |
return; | |
} | |
if (!Process::command('python3 --version') | |
->timeout(120) | |
->idleTimeout(120) | |
->run() | |
->successful()) { | |
$result = Process::command('apt-get install -y python3') | |
->timeout(120) | |
->idleTimeout(120) | |
->run(); | |
if (!$result->successful()) { | |
throw new \RuntimeException("Failed to install Python: " . $result->output()); | |
} | |
} | |
if (!Process::command('python3 -m pip --version') | |
->timeout(120) | |
->idleTimeout(120) | |
->run() | |
->successful()) { | |
$result = Process::command('apt-get install -y python3-pip') | |
->timeout(120) | |
->idleTimeout(120) | |
->run(); | |
if (!$result->successful()) { | |
throw new \RuntimeException("Failed to install pip: " . $result->output()); | |
} | |
} | |
if (!Process::command('python3 -m pip show numpy') | |
->run() | |
->successful()) { | |
$result = Process::command('python3 -m pip install numpy') | |
->timeout(120) | |
->idleTimeout(120) | |
->run(); | |
if (!$result->successful()) { | |
throw new \RuntimeException("Failed to install Python dependencies: " . $result->errorOutput()); | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment