Skip to content

Instantly share code, notes, and snippets.

@ZachWatkins
Created May 20, 2025 20:52
Show Gist options
  • Save ZachWatkins/7afdc8d43900327f54f0a0d19c440a7e to your computer and use it in GitHub Desktop.
Save ZachWatkins/7afdc8d43900327f54f0a0d19c440a7e to your computer and use it in GitHub Desktop.
Laravel Python Service Class
<?php
namespace App\Contracts;
interface PythonProvider
{
public function run(string $filename, array $options): string;
public function install(): void;
}
<?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