This guide explains how to create and use a Python companion application that works across different platforms (Web, Desktop) using the serious_python package.
your_flutter_project/
├── python_companion/
│ ├── desktop/
│ │ ├── __init__.py
│ │ └── companion_server.py # Flask server for desktop
│ ├── web/
│ │ ├── __init__.py
│ │ └── command_handler.py # Command handling for web
│ ├── functionality/
│ │ ├── __init__.py
│ │ └── your_functions.py # Shared functionality
│ ├── requirements/
│ │ ├── base.txt # Shared dependencies
│ │ ├── desktop.txt # Desktop-specific requirements
│ │ └── web.txt # Web-specific (Pyodide) requirements
│ ├── python_companion_desktop.py # Desktop entry point
│ └── python_companion_web.py # Web entry point
# requirements/base.txt
numpy>=1.20.0
scipy>=1.7.0
# requirements/web.txt
-r base.txt
# Only Pyodide-compatible versions
h5py==3.8.0
# requirements/desktop.txt
-r base.txt
flask>=2.0.0
h5py==3.9.0
- Desktop Implementation (Flask Server):
# desktop/companion_server.py
from flask import Flask, request, jsonify
import functionality
app = Flask(__name__)
@app.route('/your_endpoint', methods=['POST'])
def your_endpoint():
result = functionality.your_function(request.json)
return jsonify(result)
def run_server():
app.run(port=50001, debug=False, use_reloader=False)
- Web Implementation (Pyodide):
# web/command_handler.py
import json
import functionality
_command_functions = {
"your_command": lambda data: functionality.your_function(data),
}
def handle_command(command: str, data):
command_function = _command_functions.get(command)
try:
loaded_data = json.loads(data)
except:
loaded_data = data
return command_function(data)
- Shared Functionality:
# functionality/your_functions.py
def your_function(json_data):
# Your implementation
return {"result": "success"}
- Entry Points:
# python_companion_desktop.py
from desktop import run_server
if __name__ == '__main__':
run_server()
# python_companion_web.py
import os
from web import handle_command
if __name__ == '__main__':
command = os.environ.get("PYODIDE_COMMAND", "")
data = os.environ.get("PYODIDE_DATA", None)
pyodide_result = handle_command(command, data)
Package your Python companion for different platforms:
# For Web (Pyodide)
dart run serious_python:main package \
--asset assets/python_companion.zip python_companion/ \
-p Pyodide \
--requirements "-r,python_companion/requirements/web.txt"
# For Desktop (Linux)
dart run serious_python:main package \
--asset assets/python_companion.zip python_companion/ \
-p Linux \
--requirements "-r,python_companion/requirements/desktop.txt"
- Add serious_python to your pubspec.yaml:
dependencies:
serious_python: ^latest_version
- Create a service class:
class PythonCompanionService {
Future<Either<Exception, Map<String, dynamic>>> callPythonFunction(List<double> data);
}
class PythonCompanionServiceWeb implements PythonCompanionService {
Future<Either<Exception, Map<String, dynamic>>> callPythonFunction(List<double> data) async {
final String? result = await SeriousPython.run(
'assets/python_companion.zip',
appFileName: 'python_companion_web.py',
modulePaths: ['python_companion/web', 'python_companion/functionality'],
environmentVariables:
{
'PYODIDE_COMMAND': 'your_command',
'PYODIDE_DATA': jsonEncode({'data': data})
},
sync: true,
);
if (result == null || result.isEmpty) {
return left(Exception('Failed to execute Python function'));
}
return right(jsonDecode(result));
}
}
class PythonCompanionServiceDesktop implements PythonCompanionService {
@override
Future<void> startServer() async {
try {
// Start the Python server
await SeriousPython.run(
'assets/python_companion.zip',
appFileName: 'python_companion_desktop.py',
);
// Wait for server to be ready, e.g. by checking the health endpoint
await _waitForServer();
_isServerRunning = true;
} catch (e) {
throw Exception('Failed to start Python server: $e');
}
}
@override
Future<Either<Exception, Map<String, dynamic>>> callPythonFunction(List<double> data) async {
try {
final response = await http.post(
Uri.parse('$_baseUrl/your_endpoint'),
headers: {'Content-Type': 'application/json'},
body: jsonEncode({'data': data}),
);
if (response.statusCode != 200) {
return left(Exception('Server error: ${response.statusCode}'));
}
return right(jsonDecode(response.body));
} catch (e) {
return left(Exception('Error calling Python function: $e'));
}
}
}
- Use in your Flutter app:
final pythonService = PythonCompanionService();
void yourFunction() async {
final result = await pythonService.callPythonFunction([1.0, 2.0, 3.0]);
result.fold(
(error) => print('Error: $error'),
(success) => print('Success: $success'),
);
}
-
Web Compatibility: Ensure all Python packages used in web implementation are Pyodide-compatible.
-
Package Versions: Use platform-specific package versions when needed (e.g., different h5py versions for web and desktop).
-
Error Handling: Implement proper error handling in both Python and Dart code.
-
Data Transfer: Use JSON for data transfer between Flutter and Python.
-
Resource Management: Properly manage resources (close files, connections, etc.).
- Module Import Issues: Ensure correct module paths and dependencies.
- Platform Compatibility: Check package compatibility for each platform.
- Port Conflicts: For desktop, ensure the Flask server port (50001) is available.
- Memory Management: Be mindful of memory usage, especially with large data operations.
- Keep shared functionality platform-independent
- Implement proper error handling and logging
- Use type hints and documentation
- Follow platform-specific conventions
- Test on all target platforms