switch from runner to ipc and long-running sidecar process (#21)

This commit is contained in:
Josh Thomas 2024-12-11 16:16:40 -06:00 committed by GitHub
parent 4c10afb602
commit 235bb4419d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 556 additions and 281 deletions

View file

@ -4,6 +4,7 @@ version = "0.0.0"
edition = "2021"
[dependencies]
djls-ipc = { workspace = true }
djls-python = { workspace = true }
serde = { workspace = true }

View file

@ -1,9 +1,7 @@
use djls_python::{Python, RunnerError, ScriptRunner};
use djls_ipc::{parse_json_response, JsonResponse, PythonProcess, TransportError};
use serde::Deserialize;
use std::fmt;
use crate::scripts;
#[derive(Debug)]
pub struct App(String);
@ -27,8 +25,16 @@ struct InstalledAppsCheck {
has_app: bool,
}
impl ScriptRunner for InstalledAppsCheck {
const SCRIPT: &'static str = scripts::INSTALLED_APPS_CHECK;
impl TryFrom<JsonResponse> for InstalledAppsCheck {
type Error = TransportError;
fn try_from(response: JsonResponse) -> Result<Self, Self::Error> {
response
.data()
.clone()
.ok_or_else(|| TransportError::Process("No data in response".to_string()))
.and_then(|data| serde_json::from_value(data).map_err(TransportError::Json))
}
}
impl Apps {
@ -48,8 +54,10 @@ impl Apps {
self.apps().iter()
}
pub fn check_installed(py: &Python, app: &str) -> Result<bool, RunnerError> {
let result = InstalledAppsCheck::run_with_py_args(py, app)?;
pub fn check_installed(python: &mut PythonProcess, app: &str) -> Result<bool, TransportError> {
let response = python.send("installed_apps_check", Some(vec![app.to_string()]))?;
let response = parse_json_response(response)?;
let result = InstalledAppsCheck::try_from(response)?;
Ok(result.has_app)
}
}

View file

@ -1,14 +1,15 @@
use crate::apps::Apps;
use crate::gis::{check_gis_setup, GISError};
use crate::scripts;
use crate::templates::TemplateTags;
use djls_python::{ImportCheck, Python, RunnerError, ScriptRunner};
use djls_ipc::{parse_json_response, JsonResponse, PythonProcess, TransportError};
use djls_python::{ImportCheck, Python};
use serde::Deserialize;
use std::fmt;
#[derive(Debug)]
pub struct DjangoProject {
py: Python,
python: PythonProcess,
settings_module: String,
installed_apps: Apps,
templatetags: TemplateTags,
@ -20,54 +21,67 @@ struct DjangoSetup {
templatetags: TemplateTags,
}
impl ScriptRunner for DjangoSetup {
const SCRIPT: &'static str = scripts::DJANGO_SETUP;
impl DjangoSetup {
pub fn setup(python: &mut PythonProcess) -> Result<JsonResponse, ProjectError> {
let response = python.send("django_setup", None)?;
let response = parse_json_response(response)?;
Ok(response)
}
}
impl DjangoProject {
fn new(
py: Python,
python: PythonProcess,
settings_module: String,
installed_apps: Apps,
templatetags: TemplateTags,
) -> Self {
Self {
py,
python,
settings_module,
installed_apps,
templatetags,
}
}
pub fn setup() -> Result<Self, ProjectError> {
pub fn setup(mut python: PythonProcess) -> Result<Self, ProjectError> {
let settings_module =
std::env::var("DJANGO_SETTINGS_MODULE").expect("DJANGO_SETTINGS_MODULE must be set");
let py = Python::initialize()?;
let py = Python::setup(&mut python)?;
let has_django = ImportCheck::check(&py, "django")?;
let has_django = ImportCheck::check(&mut python, Some(vec!["django".to_string()]))?;
if !has_django {
return Err(ProjectError::DjangoNotFound);
}
if !check_gis_setup(&py)? {
if !check_gis_setup(&mut python)? {
eprintln!("Warning: GeoDjango detected but GDAL is not available.");
eprintln!("Django initialization will be skipped. Some features may be limited.");
eprintln!("To enable full functionality, please install GDAL and other GeoDjango prerequisites.");
return Ok(Self {
py,
python,
settings_module,
installed_apps: Apps::default(),
templatetags: TemplateTags::default(),
});
}
let setup = DjangoSetup::run_with_py(&py)?;
let response = DjangoSetup::setup(&mut python)?;
let setup: DjangoSetup = response
.data()
.clone()
.ok_or_else(|| TransportError::Process("No data in response".to_string()))
.and_then(|data| serde_json::from_value(data).map_err(TransportError::Json))?;
Ok(Self::new(
py,
python,
settings_module,
Apps::from_strings(setup.installed_apps.to_vec()),
setup.templatetags,
@ -110,8 +124,11 @@ pub enum ProjectError {
Json(#[from] serde_json::Error),
#[error(transparent)]
Python(#[from] djls_python::PythonError),
Packaging(#[from] djls_python::PackagingError),
#[error(transparent)]
Runner(#[from] RunnerError),
Python(#[from] djls_python::PythonError),
#[error("Transport error: {0}")]
Transport(#[from] TransportError),
}

View file

@ -1,9 +1,9 @@
use crate::apps::Apps;
use djls_python::{Python, RunnerError};
use djls_ipc::{PythonProcess, TransportError};
use std::process::Command;
pub fn check_gis_setup(py: &Python) -> Result<bool, GISError> {
let has_geodjango = Apps::check_installed(py, "django.contrib.gis")?;
pub fn check_gis_setup(python: &mut PythonProcess) -> Result<bool, GISError> {
let has_geodjango = Apps::check_installed(python, "django.contrib.gis")?;
let gdal_is_installed = Command::new("gdalinfo")
.arg("--version")
.output()
@ -21,6 +21,6 @@ pub enum GISError {
#[error("JSON parsing error: {0}")]
Json(#[from] serde_json::Error),
#[error(transparent)]
Runner(#[from] RunnerError),
#[error("Transport error: {0}")]
Transport(#[from] TransportError),
}

View file

@ -1,7 +1,6 @@
mod apps;
mod django;
mod gis;
mod scripts;
mod templates;
pub use django::DjangoProject;

View file

@ -1,4 +0,0 @@
use djls_python::include_script;
pub const DJANGO_SETUP: &str = include_script!("django_setup");
pub const INSTALLED_APPS_CHECK: &str = include_script!("installed_apps_check");