diff --git a/crates/djls-django/src/apps.rs b/crates/djls-django/src/apps.rs index fa01aabd..bed027bd 100644 --- a/crates/djls-django/src/apps.rs +++ b/crates/djls-django/src/apps.rs @@ -1,5 +1,3 @@ -use djls_ipc::v1::*; -use djls_ipc::{ProcessError, PythonProcess}; use std::fmt; #[derive(Debug)] @@ -36,24 +34,6 @@ impl Apps { pub fn iter(&self) -> impl Iterator { self.apps().iter() } - - pub fn check_installed(python: &mut PythonProcess, app: &str) -> Result { - let request = messages::Request { - command: Some(messages::request::Command::CheckAppInstalled( - check::AppInstalledRequest { - app_name: app.to_string(), - }, - )), - }; - - let response = python.send(request).map_err(ProcessError::Transport)?; - - match response.result { - Some(messages::response::Result::CheckAppInstalled(response)) => Ok(response.passed), - Some(messages::response::Result::Error(e)) => Err(ProcessError::Health(e.message)), - _ => Err(ProcessError::Response), - } - } } impl fmt::Display for Apps { diff --git a/crates/djls-django/src/django.rs b/crates/djls-django/src/django.rs index 3b0ede91..4947212b 100644 --- a/crates/djls-django/src/django.rs +++ b/crates/djls-django/src/django.rs @@ -1,4 +1,3 @@ -use crate::gis::{check_gis_setup, GISError}; use djls_ipc::v1::*; use djls_ipc::IpcCommand; use djls_ipc::{ProcessError, PythonProcess, TransportError}; @@ -24,16 +23,24 @@ impl DjangoProject { pub fn setup(mut python: PythonProcess) -> Result { let py = Python::setup(&mut python)?; - 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."); + match check::GeoDjangoPrereqsRequest::execute(&mut python)?.result { + Some(messages::response::Result::CheckGeodjangoPrereqs(response)) => { + if !response.passed { + 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, - version: String::new(), - }); + return Ok(Self { + py, + python, + version: String::new(), + }); + } + } + Some(messages::response::Result::Error(e)) => Err(ProcessError::Health(e.message))?, + _ => Err(ProcessError::Response)?, } let response = django::GetProjectInfoRequest::execute(&mut python)?; @@ -77,8 +84,6 @@ pub enum ProjectError { DjangoNotFound, #[error("IO error: {0}")] Io(#[from] std::io::Error), - #[error("GIS error: {0}")] - Gis(#[from] GISError), #[error("JSON parsing error: {0}")] Json(#[from] serde_json::Error), #[error(transparent)] diff --git a/crates/djls-django/src/gis.rs b/crates/djls-django/src/gis.rs deleted file mode 100644 index bd817bdd..00000000 --- a/crates/djls-django/src/gis.rs +++ /dev/null @@ -1,26 +0,0 @@ -use crate::apps::Apps; -use djls_ipc::{ProcessError, PythonProcess, TransportError}; -use std::process::Command; - -pub fn check_gis_setup(python: &mut PythonProcess) -> Result { - let has_geodjango = Apps::check_installed(python, "django.contrib.gis")?; - let gdal_is_installed = Command::new("gdalinfo") - .arg("--version") - .output() - .map(|output| output.status.success()) - .unwrap_or(false); - - Ok(!has_geodjango || gdal_is_installed) -} - -#[derive(Debug, thiserror::Error)] -pub enum GISError { - #[error("IO error: {0}")] - Io(#[from] std::io::Error), - #[error("JSON parsing error: {0}")] - Json(#[from] serde_json::Error), - #[error("Process error: {0}")] - Process(#[from] ProcessError), - #[error("Transport error: {0}")] - Transport(#[from] TransportError), -} diff --git a/crates/djls-django/src/lib.rs b/crates/djls-django/src/lib.rs index 6b6bf8fd..918eee39 100644 --- a/crates/djls-django/src/lib.rs +++ b/crates/djls-django/src/lib.rs @@ -1,6 +1,5 @@ mod apps; mod django; -mod gis; mod templates; pub use django::DjangoProject; diff --git a/crates/djls-ipc/src/commands.rs b/crates/djls-ipc/src/commands.rs index 8d739e6f..3fac6ac4 100644 --- a/crates/djls-ipc/src/commands.rs +++ b/crates/djls-ipc/src/commands.rs @@ -29,6 +29,22 @@ impl IpcCommand for v1::check::HealthRequest { } } +impl IpcCommand for v1::check::GeoDjangoPrereqsRequest { + fn into_request(&self) -> messages::Request { + messages::Request { + command: Some(messages::request::Command::CheckGeodjangoPrereqs(*self)), + } + } + + fn from_response(response: messages::Response) -> Result { + match response.result { + Some(messages::response::Result::CheckGeodjangoPrereqs(_)) => Ok(response), + Some(messages::response::Result::Error(e)) => Err(ProcessError::Health(e.message)), + _ => Err(ProcessError::Response), + } + } +} + impl IpcCommand for v1::python::GetEnvironmentRequest { fn into_request(&self) -> messages::Request { messages::Request { diff --git a/proto/v1/check.proto b/proto/v1/check.proto index d64635cc..270e5ddf 100644 --- a/proto/v1/check.proto +++ b/proto/v1/check.proto @@ -8,10 +8,8 @@ message HealthResponse { optional string error = 2; } -message AppInstalledRequest { - string app_name = 1; -} -message AppInstalledResponse { +message GeoDjangoPrereqsRequest {} +message GeoDjangoPrereqsResponse { bool passed = 1; optional string error = 2; } diff --git a/proto/v1/messages.proto b/proto/v1/messages.proto index ca6ee910..91821db8 100644 --- a/proto/v1/messages.proto +++ b/proto/v1/messages.proto @@ -9,7 +9,7 @@ import "v1/python.proto"; message Request { oneof command { check.HealthRequest check__health = 1; - check.AppInstalledRequest check__app_installed = 2; + check.GeoDjangoPrereqsRequest check__geodjango_prereqs = 2; python.GetEnvironmentRequest python__get_environment = 1000; django.GetProjectInfoRequest django__get_project_info = 2000; } @@ -18,7 +18,7 @@ message Request { message Response { oneof result { check.HealthResponse check__health = 1; - check.AppInstalledResponse check__app_installed = 2; + check.GeoDjangoPrereqsResponse check__geodjango_prereqs = 2; python.GetEnvironmentResponse python__get_environment = 1000; django.GetProjectInfoResponse django__get_project_info = 2000; Error error = 9000; diff --git a/python/djls/handlers.py b/python/djls/handlers.py index 648ae97d..84c2b1f6 100644 --- a/python/djls/handlers.py +++ b/python/djls/handlers.py @@ -3,6 +3,7 @@ import importlib.metadata import inspect import os +import subprocess import sys import sysconfig import traceback @@ -91,16 +92,23 @@ async def check__health(_request: check_pb2.HealthRequest) -> check_pb2.HealthRe return check_pb2.HealthResponse(passed=True) -@proto_handler( - check_pb2.AppInstalledRequest, - error=messages_pb2.Error( - code=messages_pb2.Error.DJANGO_ERROR, message="App is not in INSTALLED_APPS" - ), -) -async def check__app_installed( - request: check_pb2.AppInstalledRequest, -) -> check_pb2.AppInstalledResponse: - return check_pb2.AppInstalledResponse(passed=apps.is_installed(request.app_name)) +@proto_handler(check_pb2.GeoDjangoPrereqsRequest) +async def check__geodjango_prereqs( + request: check_pb2.GeoDjangoPrereqsRequest, +) -> check_pb2.GeoDjangoPrereqsResponse: + has_geodjango = apps.is_installed("django.contrib.gis") + + try: + gdal_process = subprocess.run( + ["gdalinfo", "--version"], capture_output=True, check=False + ) + gdal_is_installed = gdal_process.returncode == 0 + except FileNotFoundError: + gdal_is_installed = False + + return check_pb2.GeoDjangoPrereqsResponse( + passed=(not has_geodjango) or gdal_is_installed + ) @proto_handler(python_pb2.GetEnvironmentRequest) diff --git a/python/djls/proto/v1/check_pb2.py b/python/djls/proto/v1/check_pb2.py index 375ce7c1..1dce023b 100644 --- a/python/djls/proto/v1/check_pb2.py +++ b/python/djls/proto/v1/check_pb2.py @@ -28,7 +28,7 @@ -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0ev1/check.proto\x12\rdjls.v1.check\"\x0f\n\rHealthRequest\">\n\x0eHealthResponse\x12\x0e\n\x06passed\x18\x01 \x01(\x08\x12\x12\n\x05\x65rror\x18\x02 \x01(\tH\x00\x88\x01\x01\x42\x08\n\x06_error\"\'\n\x13\x41ppInstalledRequest\x12\x10\n\x08\x61pp_name\x18\x01 \x01(\t\"D\n\x14\x41ppInstalledResponse\x12\x0e\n\x06passed\x18\x01 \x01(\x08\x12\x12\n\x05\x65rror\x18\x02 \x01(\tH\x00\x88\x01\x01\x42\x08\n\x06_errorb\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0ev1/check.proto\x12\rdjls.v1.check\"\x0f\n\rHealthRequest\">\n\x0eHealthResponse\x12\x0e\n\x06passed\x18\x01 \x01(\x08\x12\x12\n\x05\x65rror\x18\x02 \x01(\tH\x00\x88\x01\x01\x42\x08\n\x06_error\"\x19\n\x17GeoDjangoPrereqsRequest\"H\n\x18GeoDjangoPrereqsResponse\x12\x0e\n\x06passed\x18\x01 \x01(\x08\x12\x12\n\x05\x65rror\x18\x02 \x01(\tH\x00\x88\x01\x01\x42\x08\n\x06_errorb\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -39,8 +39,8 @@ _globals['_HEALTHREQUEST']._serialized_end=48 _globals['_HEALTHRESPONSE']._serialized_start=50 _globals['_HEALTHRESPONSE']._serialized_end=112 - _globals['_APPINSTALLEDREQUEST']._serialized_start=114 - _globals['_APPINSTALLEDREQUEST']._serialized_end=153 - _globals['_APPINSTALLEDRESPONSE']._serialized_start=155 - _globals['_APPINSTALLEDRESPONSE']._serialized_end=223 + _globals['_GEODJANGOPREREQSREQUEST']._serialized_start=114 + _globals['_GEODJANGOPREREQSREQUEST']._serialized_end=139 + _globals['_GEODJANGOPREREQSRESPONSE']._serialized_start=141 + _globals['_GEODJANGOPREREQSRESPONSE']._serialized_end=213 # @@protoc_insertion_point(module_scope) diff --git a/python/djls/proto/v1/check_pb2.pyi b/python/djls/proto/v1/check_pb2.pyi index b6a0d595..eb0188a8 100644 --- a/python/djls/proto/v1/check_pb2.pyi +++ b/python/djls/proto/v1/check_pb2.pyi @@ -20,13 +20,11 @@ class HealthResponse(_message.Message): error: str def __init__(self, passed: bool = ..., error: _Optional[str] = ...) -> None: ... -class AppInstalledRequest(_message.Message): - __slots__ = ("app_name",) - APP_NAME_FIELD_NUMBER: _ClassVar[int] - app_name: str - def __init__(self, app_name: _Optional[str] = ...) -> None: ... +class GeoDjangoPrereqsRequest(_message.Message): + __slots__ = () + def __init__(self) -> None: ... -class AppInstalledResponse(_message.Message): +class GeoDjangoPrereqsResponse(_message.Message): __slots__ = ("passed", "error") PASSED_FIELD_NUMBER: _ClassVar[int] ERROR_FIELD_NUMBER: _ClassVar[int] diff --git a/python/djls/proto/v1/messages_pb2.py b/python/djls/proto/v1/messages_pb2.py index 5eb7e8c2..749f5090 100644 --- a/python/djls/proto/v1/messages_pb2.py +++ b/python/djls/proto/v1/messages_pb2.py @@ -31,7 +31,7 @@ from . import python_pb2 as v1_dot_python__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x11v1/messages.proto\x12\x10\x64jls.v1.messages\x1a\x0ev1/check.proto\x1a\x0fv1/django.proto\x1a\x0fv1/python.proto\"\xa6\x02\n\x07Request\x12\x35\n\rcheck__health\x18\x01 \x01(\x0b\x32\x1c.djls.v1.check.HealthRequestH\x00\x12\x42\n\x14\x63heck__app_installed\x18\x02 \x01(\x0b\x32\".djls.v1.check.AppInstalledRequestH\x00\x12I\n\x17python__get_environment\x18\xe8\x07 \x01(\x0b\x32%.djls.v1.python.GetEnvironmentRequestH\x00\x12J\n\x18\x64jango__get_project_info\x18\xd0\x0f \x01(\x0b\x32%.djls.v1.django.GetProjectInfoRequestH\x00\x42\t\n\x07\x63ommand\"\xd5\x02\n\x08Response\x12\x36\n\rcheck__health\x18\x01 \x01(\x0b\x32\x1d.djls.v1.check.HealthResponseH\x00\x12\x43\n\x14\x63heck__app_installed\x18\x02 \x01(\x0b\x32#.djls.v1.check.AppInstalledResponseH\x00\x12J\n\x17python__get_environment\x18\xe8\x07 \x01(\x0b\x32&.djls.v1.python.GetEnvironmentResponseH\x00\x12K\n\x18\x64jango__get_project_info\x18\xd0\x0f \x01(\x0b\x32&.djls.v1.django.GetProjectInfoResponseH\x00\x12)\n\x05\x65rror\x18\xa8\x46 \x01(\x0b\x32\x17.djls.v1.messages.ErrorH\x00\x42\x08\n\x06result\"\xa5\x01\n\x05\x45rror\x12*\n\x04\x63ode\x18\x01 \x01(\x0e\x32\x1c.djls.v1.messages.Error.Code\x12\x0f\n\x07message\x18\x02 \x01(\t\x12\x11\n\ttraceback\x18\x03 \x01(\t\"L\n\x04\x43ode\x12\x0b\n\x07UNKNOWN\x10\x00\x12\x13\n\x0fINVALID_REQUEST\x10\x01\x12\x10\n\x0cPYTHON_ERROR\x10\x02\x12\x10\n\x0c\x44JANGO_ERROR\x10\x03\x62\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x11v1/messages.proto\x12\x10\x64jls.v1.messages\x1a\x0ev1/check.proto\x1a\x0fv1/django.proto\x1a\x0fv1/python.proto\"\xae\x02\n\x07Request\x12\x35\n\rcheck__health\x18\x01 \x01(\x0b\x32\x1c.djls.v1.check.HealthRequestH\x00\x12J\n\x18\x63heck__geodjango_prereqs\x18\x02 \x01(\x0b\x32&.djls.v1.check.GeoDjangoPrereqsRequestH\x00\x12I\n\x17python__get_environment\x18\xe8\x07 \x01(\x0b\x32%.djls.v1.python.GetEnvironmentRequestH\x00\x12J\n\x18\x64jango__get_project_info\x18\xd0\x0f \x01(\x0b\x32%.djls.v1.django.GetProjectInfoRequestH\x00\x42\t\n\x07\x63ommand\"\xdd\x02\n\x08Response\x12\x36\n\rcheck__health\x18\x01 \x01(\x0b\x32\x1d.djls.v1.check.HealthResponseH\x00\x12K\n\x18\x63heck__geodjango_prereqs\x18\x02 \x01(\x0b\x32\'.djls.v1.check.GeoDjangoPrereqsResponseH\x00\x12J\n\x17python__get_environment\x18\xe8\x07 \x01(\x0b\x32&.djls.v1.python.GetEnvironmentResponseH\x00\x12K\n\x18\x64jango__get_project_info\x18\xd0\x0f \x01(\x0b\x32&.djls.v1.django.GetProjectInfoResponseH\x00\x12)\n\x05\x65rror\x18\xa8\x46 \x01(\x0b\x32\x17.djls.v1.messages.ErrorH\x00\x42\x08\n\x06result\"\xa5\x01\n\x05\x45rror\x12*\n\x04\x63ode\x18\x01 \x01(\x0e\x32\x1c.djls.v1.messages.Error.Code\x12\x0f\n\x07message\x18\x02 \x01(\t\x12\x11\n\ttraceback\x18\x03 \x01(\t\"L\n\x04\x43ode\x12\x0b\n\x07UNKNOWN\x10\x00\x12\x13\n\x0fINVALID_REQUEST\x10\x01\x12\x10\n\x0cPYTHON_ERROR\x10\x02\x12\x10\n\x0c\x44JANGO_ERROR\x10\x03\x62\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -39,11 +39,11 @@ if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None _globals['_REQUEST']._serialized_start=90 - _globals['_REQUEST']._serialized_end=384 - _globals['_RESPONSE']._serialized_start=387 - _globals['_RESPONSE']._serialized_end=728 - _globals['_ERROR']._serialized_start=731 - _globals['_ERROR']._serialized_end=896 - _globals['_ERROR_CODE']._serialized_start=820 - _globals['_ERROR_CODE']._serialized_end=896 + _globals['_REQUEST']._serialized_end=392 + _globals['_RESPONSE']._serialized_start=395 + _globals['_RESPONSE']._serialized_end=744 + _globals['_ERROR']._serialized_start=747 + _globals['_ERROR']._serialized_end=912 + _globals['_ERROR_CODE']._serialized_start=836 + _globals['_ERROR_CODE']._serialized_end=912 # @@protoc_insertion_point(module_scope) diff --git a/python/djls/proto/v1/messages_pb2.pyi b/python/djls/proto/v1/messages_pb2.pyi index 34b2a895..2da4b261 100644 --- a/python/djls/proto/v1/messages_pb2.pyi +++ b/python/djls/proto/v1/messages_pb2.pyi @@ -13,30 +13,30 @@ from typing import ClassVar as _ClassVar, Mapping as _Mapping, Optional as _Opti DESCRIPTOR: _descriptor.FileDescriptor class Request(_message.Message): - __slots__ = ("check__health", "check__app_installed", "python__get_environment", "django__get_project_info") + __slots__ = ("check__health", "check__geodjango_prereqs", "python__get_environment", "django__get_project_info") CHECK__HEALTH_FIELD_NUMBER: _ClassVar[int] - CHECK__APP_INSTALLED_FIELD_NUMBER: _ClassVar[int] + CHECK__GEODJANGO_PREREQS_FIELD_NUMBER: _ClassVar[int] PYTHON__GET_ENVIRONMENT_FIELD_NUMBER: _ClassVar[int] DJANGO__GET_PROJECT_INFO_FIELD_NUMBER: _ClassVar[int] check__health: _check_pb2.HealthRequest - check__app_installed: _check_pb2.AppInstalledRequest + check__geodjango_prereqs: _check_pb2.GeoDjangoPrereqsRequest python__get_environment: _python_pb2.GetEnvironmentRequest django__get_project_info: _django_pb2.GetProjectInfoRequest - def __init__(self, check__health: _Optional[_Union[_check_pb2.HealthRequest, _Mapping]] = ..., check__app_installed: _Optional[_Union[_check_pb2.AppInstalledRequest, _Mapping]] = ..., python__get_environment: _Optional[_Union[_python_pb2.GetEnvironmentRequest, _Mapping]] = ..., django__get_project_info: _Optional[_Union[_django_pb2.GetProjectInfoRequest, _Mapping]] = ...) -> None: ... + def __init__(self, check__health: _Optional[_Union[_check_pb2.HealthRequest, _Mapping]] = ..., check__geodjango_prereqs: _Optional[_Union[_check_pb2.GeoDjangoPrereqsRequest, _Mapping]] = ..., python__get_environment: _Optional[_Union[_python_pb2.GetEnvironmentRequest, _Mapping]] = ..., django__get_project_info: _Optional[_Union[_django_pb2.GetProjectInfoRequest, _Mapping]] = ...) -> None: ... class Response(_message.Message): - __slots__ = ("check__health", "check__app_installed", "python__get_environment", "django__get_project_info", "error") + __slots__ = ("check__health", "check__geodjango_prereqs", "python__get_environment", "django__get_project_info", "error") CHECK__HEALTH_FIELD_NUMBER: _ClassVar[int] - CHECK__APP_INSTALLED_FIELD_NUMBER: _ClassVar[int] + CHECK__GEODJANGO_PREREQS_FIELD_NUMBER: _ClassVar[int] PYTHON__GET_ENVIRONMENT_FIELD_NUMBER: _ClassVar[int] DJANGO__GET_PROJECT_INFO_FIELD_NUMBER: _ClassVar[int] ERROR_FIELD_NUMBER: _ClassVar[int] check__health: _check_pb2.HealthResponse - check__app_installed: _check_pb2.AppInstalledResponse + check__geodjango_prereqs: _check_pb2.GeoDjangoPrereqsResponse python__get_environment: _python_pb2.GetEnvironmentResponse django__get_project_info: _django_pb2.GetProjectInfoResponse error: Error - def __init__(self, check__health: _Optional[_Union[_check_pb2.HealthResponse, _Mapping]] = ..., check__app_installed: _Optional[_Union[_check_pb2.AppInstalledResponse, _Mapping]] = ..., python__get_environment: _Optional[_Union[_python_pb2.GetEnvironmentResponse, _Mapping]] = ..., django__get_project_info: _Optional[_Union[_django_pb2.GetProjectInfoResponse, _Mapping]] = ..., error: _Optional[_Union[Error, _Mapping]] = ...) -> None: ... + def __init__(self, check__health: _Optional[_Union[_check_pb2.HealthResponse, _Mapping]] = ..., check__geodjango_prereqs: _Optional[_Union[_check_pb2.GeoDjangoPrereqsResponse, _Mapping]] = ..., python__get_environment: _Optional[_Union[_python_pb2.GetEnvironmentResponse, _Mapping]] = ..., django__get_project_info: _Optional[_Union[_django_pb2.GetProjectInfoResponse, _Mapping]] = ..., error: _Optional[_Union[Error, _Mapping]] = ...) -> None: ... class Error(_message.Message): __slots__ = ("code", "message", "traceback")