swap from IPC architecture to PyO3 library (#45)

This commit is contained in:
Josh Thomas 2024-12-23 10:12:10 -06:00 committed by GitHub
parent df30aafde5
commit a73e912e0f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
53 changed files with 136 additions and 2224 deletions

View file

@ -4,17 +4,14 @@ resolver = "2"
[workspace.dependencies]
djls = { path = "crates/djls" }
djls-django = { path = "crates/djls-django" }
djls-ipc = { path = "crates/djls-ipc" }
djls-python = { path = "crates/djls-python" }
djls-server = { path = "crates/djls-server" }
djls-template-ast = { path = "crates/djls-template-ast" }
djls-worker = { path = "crates/djls-worker" }
anyhow = "1.0"
async-trait = "0.1"
prost = "0.13"
bytes = "1.9"
pyo3 = "0.23"
pyo3-async-runtimes = "0.23"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
thiserror = "2.0"

View file

@ -1,12 +0,0 @@
[package]
name = "djls-django"
version = "0.0.0"
edition = "2021"
[dependencies]
djls-ipc = { workspace = true }
djls-python = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
thiserror = { workspace = true }

View file

@ -1,46 +0,0 @@
use std::fmt;
#[derive(Debug)]
pub struct App(String);
impl App {
pub fn name(&self) -> &str {
&self.0
}
}
impl fmt::Display for App {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Debug, Default)]
pub struct Apps(Vec<App>);
impl Apps {
pub fn from_strings(apps: Vec<String>) -> Self {
Self(apps.into_iter().map(App).collect())
}
pub fn apps(&self) -> &[App] {
&self.0
}
pub fn has_app(&self, name: &str) -> bool {
self.apps().iter().any(|app| app.0 == name)
}
pub fn iter(&self) -> impl Iterator<Item = &App> {
self.apps().iter()
}
}
impl fmt::Display for Apps {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for app in &self.0 {
writeln!(f, " {}", app)?;
}
Ok(())
}
}

View file

@ -1,97 +0,0 @@
use djls_ipc::v1::*;
use djls_ipc::IpcCommand;
use djls_ipc::{ProcessError, PythonProcess, TransportError};
use djls_python::Python;
use std::fmt;
#[derive(Debug)]
pub struct DjangoProject {
py: Python,
python: PythonProcess,
version: String,
}
impl DjangoProject {
fn new(py: Python, python: PythonProcess, version: String) -> Self {
Self {
py,
python,
version,
}
}
pub fn setup(mut python: PythonProcess) -> Result<Self, ProjectError> {
let py = Python::setup(&mut python)?;
match commands::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(),
});
}
}
Some(messages::response::Result::Error(e)) => Err(ProcessError::Health(e.message))?,
_ => Err(ProcessError::Response)?,
}
let response = commands::django::GetProjectInfoRequest::execute(&mut python)?;
let version = match response.result {
Some(messages::response::Result::DjangoGetProjectInfo(response)) => {
response.project.unwrap().version
}
_ => {
return Err(ProjectError::Process(ProcessError::Response));
}
};
Ok(Self {
py,
python,
version,
})
}
pub fn py(&self) -> &Python {
&self.py
}
fn version(&self) -> &String {
&self.version
}
}
impl fmt::Display for DjangoProject {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "Django Project")?;
writeln!(f, "Version: {}", self.version)?;
Ok(())
}
}
#[derive(Debug, thiserror::Error)]
pub enum ProjectError {
#[error("Django is not installed or cannot be imported")]
DjangoNotFound,
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("JSON parsing error: {0}")]
Json(#[from] serde_json::Error),
#[error(transparent)]
Packaging(#[from] djls_python::PackagingError),
#[error("Process error: {0}")]
Process(#[from] ProcessError),
#[error(transparent)]
Python(#[from] djls_python::PythonError),
#[error("Transport error: {0}")]
Transport(#[from] TransportError),
}

View file

@ -1,5 +0,0 @@
mod apps;
mod django;
mod templates;
pub use django::DjangoProject;

View file

@ -1,63 +0,0 @@
use serde::Deserialize;
use std::fmt;
#[derive(Clone, Debug, Deserialize)]
pub struct TemplateTag {
name: String,
library: String,
doc: Option<String>,
}
impl fmt::Display for TemplateTag {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let library = if self.library.is_empty() {
"builtins"
} else {
&self.library
};
write!(f, "{} ({})", self.name, library)?;
writeln!(f)?;
if let Some(doc) = &self.doc {
for line in doc.trim_end().split("\n") {
writeln!(f, "{}", line)?;
}
}
Ok(())
}
}
#[derive(Debug, Default, Deserialize)]
pub struct TemplateTags(Vec<TemplateTag>);
impl TemplateTags {
pub fn tags(&self) -> &Vec<TemplateTag> {
&self.0
}
fn iter(&self) -> impl Iterator<Item = &TemplateTag> {
self.tags().iter()
}
pub fn filter_by_prefix<'a>(
&'a self,
prefix: &'a str,
) -> impl Iterator<Item = &'a TemplateTag> {
self.iter().filter(move |tag| tag.name.starts_with(prefix))
}
pub fn get_by_name(&self, name: &str) -> Option<&TemplateTag> {
self.iter().find(|tag| tag.name == name)
}
}
impl fmt::Display for TemplateTags {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for tag in &self.0 {
writeln!(f, " {}", tag)?;
}
Ok(())
}
}

View file

@ -1,19 +0,0 @@
[package]
name = "djls-ipc"
version = "0.0.0"
edition = "2021"
[dependencies]
anyhow = { workspace = true }
async-trait = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
thiserror = { workspace = true }
tokio = { workspace = true }
bytes = "1.9"
prost = "0.13"
tempfile = "3.14"
[build-dependencies]
prost-build = "0.13"

View file

@ -1,38 +0,0 @@
use std::fs;
use std::path::{Path, PathBuf};
struct Version(&'static str);
impl Version {
fn collect_protos(&self, proto_root: &Path) -> Vec<PathBuf> {
fs::read_dir(proto_root.join(self.0))
.unwrap()
.filter_map(Result::ok)
.filter(|entry| entry.path().extension().and_then(|s| s.to_str()) == Some("proto"))
.map(|entry| entry.path())
.collect()
}
}
const VERSIONS: &[Version] = &[Version("v1")];
fn main() {
let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let workspace_root = manifest_dir.parent().unwrap().parent().unwrap();
let proto_dir = workspace_root.join("proto");
let mut protos = Vec::new();
for version in VERSIONS {
protos.extend(version.collect_protos(&proto_dir));
}
prost_build::Config::new()
.compile_protos(
&protos
.iter()
.map(|p| p.to_str().unwrap())
.collect::<Vec<_>>(),
&[proto_dir],
)
.unwrap();
}

View file

@ -1,78 +0,0 @@
use crate::proto::v1::{self, messages};
use crate::{ProcessError, PythonProcess};
pub trait IpcCommand: Default {
fn into_request(&self) -> messages::Request;
fn from_response(response: messages::Response) -> Result<messages::Response, ProcessError>;
fn execute(process: &mut PythonProcess) -> Result<messages::Response, ProcessError> {
let cmd = Self::default();
let request = cmd.into_request();
let response = process.send(request).map_err(ProcessError::Transport)?;
Self::from_response(response)
}
}
impl IpcCommand for v1::commands::check::HealthRequest {
fn into_request(&self) -> messages::Request {
messages::Request {
command: Some(messages::request::Command::CheckHealth(*self)),
}
}
fn from_response(response: messages::Response) -> Result<messages::Response, ProcessError> {
match response.result {
Some(messages::response::Result::CheckHealth(_)) => Ok(response),
Some(messages::response::Result::Error(e)) => Err(ProcessError::Health(e.message)),
_ => Err(ProcessError::Response),
}
}
}
impl IpcCommand for v1::commands::check::GeoDjangoPrereqsRequest {
fn into_request(&self) -> messages::Request {
messages::Request {
command: Some(messages::request::Command::CheckGeodjangoPrereqs(*self)),
}
}
fn from_response(response: messages::Response) -> Result<messages::Response, ProcessError> {
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::commands::python::GetEnvironmentRequest {
fn into_request(&self) -> messages::Request {
messages::Request {
command: Some(messages::request::Command::PythonGetEnvironment(*self)),
}
}
fn from_response(response: messages::Response) -> Result<messages::Response, ProcessError> {
match response.result {
Some(messages::response::Result::PythonGetEnvironment(_)) => Ok(response),
Some(messages::response::Result::Error(e)) => Err(ProcessError::Health(e.message)),
_ => Err(ProcessError::Response),
}
}
}
impl IpcCommand for v1::commands::django::GetProjectInfoRequest {
fn into_request(&self) -> messages::Request {
messages::Request {
command: Some(messages::request::Command::DjangoGetProjectInfo(*self)),
}
}
fn from_response(response: messages::Response) -> Result<messages::Response, ProcessError> {
match response.result {
Some(messages::response::Result::DjangoGetProjectInfo(_)) => Ok(response),
Some(messages::response::Result::Error(e)) => Err(ProcessError::Health(e.message)),
_ => Err(ProcessError::Response),
}
}
}

View file

@ -1,11 +0,0 @@
mod commands;
mod process;
mod proto;
mod transport;
pub use commands::IpcCommand;
pub use process::ProcessError;
pub use process::PythonProcess;
pub use proto::v1;
pub use transport::Transport;
pub use transport::TransportError;

View file

@ -1,138 +0,0 @@
use crate::proto::v1::*;
use crate::transport::{Transport, TransportError};
use std::ffi::OsStr;
use std::process::{Child, Command, Stdio};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
use std::time::Duration;
use tokio::time;
#[derive(Debug)]
pub struct PythonProcess {
transport: Arc<Mutex<Transport>>,
_child: Child,
healthy: Arc<AtomicBool>,
}
impl PythonProcess {
pub fn new<I, S>(
module: &str,
args: Option<I>,
health_check_interval: Option<Duration>,
) -> Result<Self, ProcessError>
where
I: IntoIterator<Item = S>,
S: AsRef<OsStr>,
{
let mut command = Command::new("python");
command.arg("-m").arg(module);
if let Some(args) = args {
command.args(args);
}
command.stdin(Stdio::piped()).stdout(Stdio::piped());
let mut child = command.spawn().map_err(TransportError::Io)?;
let stdin = child.stdin.take().unwrap();
let stdout = child.stdout.take().unwrap();
let transport = Transport::new(stdin, stdout)?;
let process = Self {
transport: Arc::new(Mutex::new(transport)),
_child: child,
healthy: Arc::new(AtomicBool::new(true)),
};
if let Some(interval) = health_check_interval {
let transport = process.transport.clone();
let healthy = process.healthy.clone();
tokio::spawn(async move {
let mut interval = time::interval(interval);
loop {
interval.tick().await;
let _ = PythonProcess::check_health(transport.clone(), healthy.clone()).await;
}
});
}
Ok(process)
}
pub fn is_healthy(&self) -> bool {
self.healthy.load(Ordering::SeqCst)
}
pub fn send(
&mut self,
request: messages::Request,
) -> Result<messages::Response, TransportError> {
let mut transport = self.transport.lock().unwrap();
transport.send(request)
}
async fn check_health(
transport: Arc<Mutex<Transport>>,
healthy: Arc<AtomicBool>,
) -> Result<(), ProcessError> {
let request = messages::Request {
command: Some(messages::request::Command::CheckHealth(
commands::check::HealthRequest {},
)),
};
let response = tokio::time::timeout(
Duration::from_secs(5),
tokio::task::spawn_blocking(move || {
let mut transport = transport.lock().unwrap();
transport.send(request)
}),
)
.await
.map_err(|_| ProcessError::Timeout(5))?
.map_err(TransportError::Task)?
.map_err(ProcessError::Transport)?;
let result = match response.result {
Some(messages::response::Result::CheckHealth(health)) => {
if !health.passed {
let error_msg = health.error.unwrap_or_else(|| "Unknown error".to_string());
Err(ProcessError::Health(error_msg))
} else {
Ok(())
}
}
Some(messages::response::Result::Error(e)) => Err(ProcessError::Health(e.message)),
_ => Err(ProcessError::Response),
};
healthy.store(result.is_ok(), Ordering::SeqCst);
result
}
}
impl Drop for PythonProcess {
fn drop(&mut self) {
if let Ok(()) = self._child.kill() {
let _ = self._child.wait();
}
}
}
#[derive(thiserror::Error, Debug)]
pub enum ProcessError {
#[error("Health check failed: {0}")]
Health(String),
#[error("Operation timed out after {0} seconds")]
Timeout(u64),
#[error("Unexpected response type")]
Response,
#[error("Failed to acquire lock: {0}")]
Lock(String),
#[error("Process not ready: {0}")]
Ready(String),
#[error("Transport error: {0}")]
Transport(#[from] TransportError),
}

View file

@ -1,17 +0,0 @@
pub mod v1 {
pub mod commands {
include!(concat!(env!("OUT_DIR"), "/djls.v1.commands.rs"));
}
pub mod django {
include!(concat!(env!("OUT_DIR"), "/djls.v1.django.rs"));
}
pub mod messages {
include!(concat!(env!("OUT_DIR"), "/djls.v1.messages.rs"));
}
pub mod python {
include!(concat!(env!("OUT_DIR"), "/djls.v1.python.rs"));
}
}

View file

@ -1,81 +0,0 @@
use crate::process::ProcessError;
use crate::proto::v1::*;
use prost::Message;
use std::io::{BufRead, BufReader, BufWriter, Read, Write};
use std::process::{ChildStdin, ChildStdout};
use std::sync::{Arc, Mutex};
#[derive(Debug, Clone)]
pub struct Transport {
reader: Arc<Mutex<BufReader<ChildStdout>>>,
writer: Arc<Mutex<BufWriter<ChildStdin>>>,
}
impl Transport {
pub fn new(mut stdin: ChildStdin, mut stdout: ChildStdout) -> Result<Self, ProcessError> {
stdin.flush().map_err(TransportError::Io)?;
let mut ready_line = String::new();
BufReader::new(&mut stdout)
.read_line(&mut ready_line)
.map_err(TransportError::Io)?;
if ready_line.trim() != "ready" {
return Err(ProcessError::Ready("Python process not ready".to_string()));
}
Ok(Self {
reader: Arc::new(Mutex::new(BufReader::new(stdout))),
writer: Arc::new(Mutex::new(BufWriter::new(stdin))),
})
}
pub fn send(
&mut self,
message: messages::Request,
) -> Result<messages::Response, TransportError> {
let buf = message.encode_to_vec();
let mut writer = self.writer.lock().map_err(|_| {
TransportError::Io(std::io::Error::new(
std::io::ErrorKind::Other,
"Failed to acquire writer lock",
))
})?;
writer
.write_all(&(buf.len() as u32).to_be_bytes())
.map_err(TransportError::Io)?;
writer.write_all(&buf).map_err(TransportError::Io)?;
writer.flush().map_err(TransportError::Io)?;
let mut reader = self.reader.lock().map_err(|_| {
TransportError::Io(std::io::Error::new(
std::io::ErrorKind::Other,
"Failed to acquire reader lock",
))
})?;
let mut length_bytes = [0u8; 4];
reader
.read_exact(&mut length_bytes)
.map_err(TransportError::Io)?;
let length = u32::from_be_bytes(length_bytes);
let mut message_bytes = vec![0u8; length as usize];
reader
.read_exact(&mut message_bytes)
.map_err(TransportError::Io)?;
messages::Response::decode(message_bytes.as_slice())
.map_err(|e| TransportError::Decode(e.to_string()))
}
}
#[derive(thiserror::Error, Debug)]
pub enum TransportError {
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("Task error: {0}")]
Task(#[from] tokio::task::JoinError),
#[error("Failed to decode message: {0}")]
Decode(String),
}

View file

@ -1,47 +0,0 @@
from __future__ import annotations
import argparse
import asyncio
import json
from pathlib import Path
async def handle_client(reader, writer):
while True:
try:
data = await reader.readline()
if not data:
break
# Parse the incoming message
message = json.loads(data)
# Echo back with same ID but just echo the content
response = {"id": message["id"], "content": message["content"]}
writer.write(json.dumps(response).encode() + b"\n")
await writer.drain()
except Exception:
break
writer.close()
await writer.wait_closed()
async def main(ipc_path):
try:
Path(ipc_path).unlink()
except FileNotFoundError:
pass
server = await asyncio.start_unix_server(
handle_client,
path=ipc_path,
)
async with server:
await server.serve_forever()
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--ipc-path", required=True)
args = parser.parse_args()
asyncio.run(main(args.ipc_path))

View file

@ -1,13 +0,0 @@
[package]
name = "djls-python"
version = "0.0.0"
edition = "2021"
[dependencies]
djls-ipc = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
thiserror = { workspace = true }
which = "7.0"

View file

@ -1,6 +0,0 @@
mod packaging;
mod python;
pub use crate::packaging::PackagingError;
pub use crate::python::Python;
pub use crate::python::PythonError;

View file

@ -1,87 +0,0 @@
use djls_ipc::v1::*;
use djls_ipc::{ProcessError, TransportError};
use serde::Deserialize;
use std::collections::HashMap;
use std::fmt;
use std::path::PathBuf;
#[derive(Clone, Debug, Deserialize)]
pub struct Package {
dist_name: String,
dist_version: String,
dist_location: Option<PathBuf>,
}
impl From<python::Package> for Package {
fn from(p: python::Package) -> Self {
Package {
dist_name: p.dist_name,
dist_version: p.dist_version,
dist_location: p.dist_location.map(PathBuf::from),
}
}
}
impl fmt::Display for Package {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} {}", self.dist_name, self.dist_version)?;
if let Some(location) = &self.dist_location {
write!(f, " ({})", location.display())?;
}
Ok(())
}
}
#[derive(Clone, Debug, Deserialize)]
pub struct Packages(HashMap<String, Package>);
impl Packages {
pub fn packages(&self) -> Vec<&Package> {
self.0.values().collect()
}
}
impl From<HashMap<String, python::Package>> for Packages {
fn from(packages: HashMap<String, python::Package>) -> Self {
Packages(packages.into_iter().map(|(k, v)| (k, v.into())).collect())
}
}
impl FromIterator<(String, Package)> for Packages {
fn from_iter<T: IntoIterator<Item = (String, Package)>>(iter: T) -> Self {
Self(HashMap::from_iter(iter))
}
}
impl fmt::Display for Packages {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut packages: Vec<_> = self.packages();
packages.sort_by(|a, b| a.dist_name.cmp(&b.dist_name));
if packages.is_empty() {
writeln!(f, " (no packages installed)")?;
} else {
for package in packages {
writeln!(f, "{}", package)?;
}
}
Ok(())
}
}
#[derive(Debug, thiserror::Error)]
pub enum PackagingError {
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("JSON parsing error: {0}")]
Json(#[from] serde_json::Error),
#[error("Transport error: {0}")]
Transport(#[from] TransportError),
#[error("Process error: {0}")]
Process(#[from] ProcessError),
#[error("UTF-8 conversion error: {0}")]
Utf8(#[from] std::string::FromUtf8Error),
}

View file

@ -1,169 +0,0 @@
use crate::packaging::{Packages, PackagingError};
use djls_ipc::v1::*;
use djls_ipc::IpcCommand;
use djls_ipc::{ProcessError, PythonProcess, TransportError};
use serde::Deserialize;
use std::fmt;
use std::path::PathBuf;
#[derive(Clone, Debug, Deserialize)]
pub struct VersionInfo {
major: u8,
minor: u8,
micro: u8,
releaselevel: ReleaseLevel,
serial: Option<String>,
}
impl From<python::VersionInfo> for VersionInfo {
fn from(v: python::VersionInfo) -> Self {
Self {
major: v.major as u8,
minor: v.minor as u8,
micro: v.micro as u8,
releaselevel: v.releaselevel().into(),
serial: Some(v.serial.to_string()),
}
}
}
#[derive(Clone, Debug, Deserialize)]
pub enum ReleaseLevel {
Alpha,
Beta,
Candidate,
Final,
}
impl From<python::ReleaseLevel> for ReleaseLevel {
fn from(level: python::ReleaseLevel) -> Self {
match level {
python::ReleaseLevel::Alpha => ReleaseLevel::Alpha,
python::ReleaseLevel::Beta => ReleaseLevel::Beta,
python::ReleaseLevel::Candidate => ReleaseLevel::Candidate,
python::ReleaseLevel::Final => ReleaseLevel::Final,
}
}
}
impl fmt::Display for VersionInfo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}.{}.{}", self.major, self.minor, self.micro)?;
Ok(())
}
}
#[derive(Clone, Debug, Deserialize)]
pub struct SysconfigPaths {
data: PathBuf,
include: PathBuf,
platinclude: PathBuf,
platlib: PathBuf,
platstdlib: PathBuf,
purelib: PathBuf,
scripts: PathBuf,
stdlib: PathBuf,
}
impl fmt::Display for SysconfigPaths {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "data: {}", self.data.display())?;
writeln!(f, "include: {}", self.include.display())?;
writeln!(f, "platinclude: {}", self.platinclude.display())?;
writeln!(f, "platlib: {}", self.platlib.display())?;
writeln!(f, "platstdlib: {}", self.platstdlib.display())?;
writeln!(f, "purelib: {}", self.purelib.display())?;
writeln!(f, "scripts: {}", self.scripts.display())?;
write!(f, "stdlib: {}", self.stdlib.display())
}
}
#[derive(Clone, Debug, Deserialize)]
pub struct Python {
version_info: VersionInfo,
sysconfig_paths: SysconfigPaths,
sys_prefix: PathBuf,
sys_base_prefix: PathBuf,
sys_executable: PathBuf,
sys_path: Vec<PathBuf>,
packages: Packages,
}
impl Python {
pub fn setup(python: &mut PythonProcess) -> Result<Self, PythonError> {
let response = commands::python::GetEnvironmentRequest::execute(python)?;
match response.result {
Some(messages::response::Result::PythonGetEnvironment(response)) => response
.python
.ok_or_else(|| PythonError::Process(ProcessError::Response))
.map(Into::into),
_ => Err(PythonError::Process(ProcessError::Response)),
}
}
}
impl From<python::Python> for Python {
fn from(p: python::Python) -> Self {
let sys = p.sys.unwrap();
let sysconfig = p.sysconfig.unwrap();
let site = p.site.unwrap();
Self {
version_info: sys.version_info.unwrap_or_default().into(),
sysconfig_paths: SysconfigPaths {
data: PathBuf::from(sysconfig.data),
include: PathBuf::from(sysconfig.include),
platinclude: PathBuf::from(sysconfig.platinclude),
platlib: PathBuf::from(sysconfig.platlib),
platstdlib: PathBuf::from(sysconfig.platstdlib),
purelib: PathBuf::from(sysconfig.purelib),
scripts: PathBuf::from(sysconfig.scripts),
stdlib: PathBuf::from(sysconfig.stdlib),
},
sys_prefix: PathBuf::from(sys.prefix),
sys_base_prefix: PathBuf::from(sys.base_prefix),
sys_executable: PathBuf::from(sys.executable),
sys_path: sys.path.into_iter().map(PathBuf::from).collect(),
packages: site.packages.into(),
}
}
}
impl fmt::Display for Python {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "Version: {}", self.version_info)?;
writeln!(f, "Executable: {}", self.sys_executable.display())?;
writeln!(f, "Prefix: {}", self.sys_prefix.display())?;
writeln!(f, "Base Prefix: {}", self.sys_base_prefix.display())?;
writeln!(f, "Paths:")?;
for path in &self.sys_path {
writeln!(f, "{}", path.display())?;
}
writeln!(f, "Sysconfig Paths:")?;
write!(f, "{}", self.sysconfig_paths)?;
writeln!(f, "\nInstalled Packages:")?;
write!(f, "{}", self.packages)
}
}
#[derive(Debug, thiserror::Error)]
pub enum PythonError {
#[error("Python execution failed: {0}")]
Execution(String),
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("JSON parsing error: {0}")]
Json(#[from] serde_json::Error),
#[error("Packaging error: {0}")]
Packaging(#[from] PackagingError),
#[error("Integer parsing error: {0}")]
Parse(#[from] std::num::ParseIntError),
#[error("Process error: {0}")]
Process(#[from] ProcessError),
#[error("Failed to locate Python executable: {0}")]
PythonNotFound(#[from] which::Error),
#[error("Transport error: {0}")]
Transport(#[from] TransportError),
#[error("UTF-8 conversion error: {0}")]
Utf8(#[from] std::string::FromUtf8Error),
}

View file

@ -4,9 +4,6 @@ version = "0.1.0"
edition = "2021"
[dependencies]
djls-django = { workspace = true }
djls-ipc = { workspace = true }
djls-python = { workspace = true }
djls-template-ast = { workspace = true }
djls-worker = { workspace = true }

View file

@ -6,8 +6,6 @@ mod tasks;
use crate::notifier::TowerLspNotifier;
use crate::server::{DjangoLanguageServer, LspNotification, LspRequest};
use anyhow::Result;
use djls_django::DjangoProject;
use djls_ipc::PythonProcess;
use std::sync::Arc;
use tokio::sync::RwLock;
use tower_lsp::jsonrpc::Result as LspResult;
@ -81,15 +79,13 @@ impl LanguageServer for TowerLspBackend {
}
}
pub async fn serve(python: PythonProcess) -> Result<()> {
let django = DjangoProject::setup(python)?;
pub async fn serve() -> Result<()> {
let stdin = tokio::io::stdin();
let stdout = tokio::io::stdout();
let (service, socket) = LspService::build(|client| {
let notifier = Box::new(TowerLspNotifier::new(client.clone()));
let server = DjangoLanguageServer::new(django, notifier);
let server = DjangoLanguageServer::new(notifier);
TowerLspBackend {
server: Arc::new(RwLock::new(server)),
}

View file

@ -2,7 +2,6 @@ use crate::documents::Store;
use crate::notifier::Notifier;
use crate::tasks::DebugTask;
use anyhow::Result;
use djls_django::DjangoProject;
use djls_worker::Worker;
use std::sync::Arc;
use std::time::Duration;
@ -24,18 +23,16 @@ pub enum LspNotification {
}
pub struct DjangoLanguageServer {
django: DjangoProject,
notifier: Arc<Box<dyn Notifier>>,
documents: Store,
worker: Worker,
}
impl DjangoLanguageServer {
pub fn new(django: DjangoProject, notifier: Box<dyn Notifier>) -> Self {
pub fn new(notifier: Box<dyn Notifier>) -> Self {
let notifier = Arc::new(notifier);
Self {
django,
notifier,
documents: Store::new(),
worker: Worker::new(),
@ -125,10 +122,6 @@ impl DjangoLanguageServer {
LspNotification::Initialized(_) => {
self.notifier
.log_message(MessageType::INFO, "server initialized!")?;
self.notifier
.log_message(MessageType::INFO, &format!("\n{}", self.django.py()))?;
self.notifier
.log_message(MessageType::INFO, &format!("\n{}", self.django))?;
Ok(())
}
LspNotification::Shutdown => Ok(()),

View file

@ -3,12 +3,16 @@ name = "djls"
version = "5.1.0-alpha.0"
edition = "2021"
[lib]
name = "djls"
crate-type = ["cdylib"]
[dependencies]
djls-django = { workspace = true }
djls-ipc = { workspace = true }
djls-server = { workspace = true }
anyhow = { workspace = true }
pyo3 = { workspace = true, features = ["extension-module"] }
pyo3-async-runtimes = { workspace = true, features = ["tokio-runtime"] }
serde_json = { workspace = true }
tokio = { workspace = true }

113
crates/djls/src/lib.rs Normal file
View file

@ -0,0 +1,113 @@
mod commands;
use crate::commands::Serve;
use anyhow::Result;
use clap::{Parser, Subcommand};
use pyo3::prelude::*;
use std::env;
use std::process::ExitCode;
#[derive(Parser)]
#[command(name = "djls")]
#[command(version, about, long_about = None)]
pub struct Cli {
#[command(subcommand)]
command: Command,
#[command(flatten)]
args: Args,
}
#[derive(Debug, Subcommand)]
enum Command {
/// Start the LSP server
Serve(Serve),
}
#[derive(Parser)]
pub struct Args {
#[command(flatten)]
global: GlobalArgs,
}
#[derive(Parser, Debug, Clone)]
struct GlobalArgs {
/// Do not print any output.
#[arg(global = true, long, short, conflicts_with = "verbose")]
pub quiet: bool,
/// Use verbose output.
#[arg(global = true, action = clap::ArgAction::Count, long, short, conflicts_with = "quiet")]
pub verbose: u8,
}
#[pyfunction]
fn cli_entrypoint(_py: Python) -> PyResult<()> {
// Skip python interpreter and script path, add command name
let args: Vec<String> = std::iter::once("djls".to_string())
.chain(env::args().skip(2))
.collect();
let runtime = tokio::runtime::Runtime::new().unwrap();
let local = tokio::task::LocalSet::new();
local.block_on(&runtime, async move {
tokio::select! {
// The main CLI program
result = cli_main(args) => {
match result {
Ok(code) => {
if code != ExitCode::SUCCESS {
std::process::exit(1);
}
Ok::<(), PyErr>(())
}
Err(e) => {
eprintln!("Error: {}", e);
if let Some(source) = e.source() {
eprintln!("Caused by: {}", source);
}
std::process::exit(1);
}
}
}
// Ctrl+C handling
_ = tokio::signal::ctrl_c() => {
println!("\nReceived Ctrl+C, shutting down...");
// Cleanup code here if needed
std::process::exit(130); // Standard Ctrl+C exit code
}
// SIGTERM handling (Unix only)
_ = async {
#[cfg(unix)]
{
use tokio::signal::unix::{signal, SignalKind};
let mut term = signal(SignalKind::terminate()).unwrap();
term.recv().await;
}
} => {
println!("\nReceived termination signal, shutting down...");
std::process::exit(143); // Standard SIGTERM exit code
}
}
})?;
Ok(())
}
async fn cli_main(args: Vec<String>) -> Result<ExitCode> {
let cli = Cli::try_parse_from(args).unwrap_or_else(|e| {
e.exit();
});
match cli.command {
Command::Serve(_serve) => djls_server::serve().await?,
}
Ok(ExitCode::SUCCESS)
}
#[pymodule]
fn djls(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(cli_entrypoint, m)?)?;
Ok(())
}

View file

@ -1,54 +0,0 @@
mod commands;
use crate::commands::Serve;
use anyhow::Result;
use clap::{Parser, Subcommand};
use djls_ipc::PythonProcess;
use std::ffi::OsStr;
use std::process::ExitCode;
#[derive(Parser)]
#[command(name = "djls")]
#[command(version, about, long_about = None)]
pub struct Cli {
#[command(subcommand)]
command: Command,
#[command(flatten)]
args: Args,
}
#[derive(Debug, Subcommand)]
enum Command {
/// Start the LSP server
Serve(Serve),
}
#[derive(Parser)]
pub struct Args {
#[command(flatten)]
global: GlobalArgs,
}
#[derive(Parser, Debug, Clone)]
struct GlobalArgs {
/// Do not print any output.
#[arg(global = true, long, short, conflicts_with = "verbose")]
pub quiet: bool,
/// Use verbose output.
#[arg(global = true, action = clap::ArgAction::Count, long, short, conflicts_with = "quiet")]
pub verbose: u8,
}
#[tokio::main]
async fn main() -> Result<ExitCode> {
let cli = Cli::parse();
match cli.command {
Command::Serve(_serve) => {
let python = PythonProcess::new::<Vec<&OsStr>, &OsStr>("djls_agent", None, None)?;
djls_server::serve(python).await?
}
}
Ok(ExitCode::SUCCESS)
}

View file

@ -1 +0,0 @@
# djls-agent

View file

@ -1,47 +0,0 @@
[build-system]
build-backend = "hatchling.build"
requires = ["hatchling"]
[dependency-groups]
dev = [
"django-stubs>=5.1.1",
"ruff>=0.8.2",
]
[project]
name = "djls-agent"
version = "5.1.0a0"
description = "Python agent for django-language-server"
readme = "README.md"
authors = [
{ name = "Josh Thomas", email = "josh@joshthomas.dev" }
]
requires-python = ">=3.9"
dependencies = [
"django>=4.2",
"protobuf>=5.29.1",
]
classifiers = [
"Development Status :: 3 - Alpha",
"Framework :: Django",
"Framework :: Django :: 4.2",
"Framework :: Django :: 5.0",
"Framework :: Django :: 5.1",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: Implementation :: CPython",
"Topic :: Software Development",
"Topic :: Text Editors :: Integrated Development Environments (IDE)"
]
[tool.hatch.build]
packages = ["src/djls_agent"]

View file

@ -1,8 +0,0 @@
from __future__ import annotations
import importlib.metadata
try:
__version__ = importlib.metadata.version(__name__)
except importlib.metadata.PackageNotFoundError:
__version__ = "0.0.0"

View file

@ -1,127 +0,0 @@
from __future__ import annotations
import struct
import sys
from typing import Any
from typing import cast
from google.protobuf.message import Message
from .logging import configure_logging
from .proto.v1 import messages_pb2
logger = configure_logging()
class LSPAgent:
def __init__(self):
from .handlers import handlers
self.handlers = handlers
logger.debug(
"LSPAgent initialized with handlers: %s", list(self.handlers.keys())
)
async def serve(self):
print("ready", flush=True)
try:
import django
django.setup()
except Exception as e:
error_response = self.create_error(messages_pb2.Error.DJANGO_ERROR, str(e))
self.write_message(error_response)
while True:
try:
data = self.read_message()
if not data:
break
response = await self.handle_request(data)
self.write_message(response)
except Exception as e:
error_response = self.create_error(messages_pb2.Error.UNKNOWN, str(e))
self.write_message(error_response)
def read_message(self) -> bytes | None:
length_bytes = sys.stdin.buffer.read(4)
logger.debug("Read length bytes: %r", length_bytes)
if not length_bytes:
return None
length = struct.unpack(">I", length_bytes)[0]
logger.debug("Unpacked length: %d", length)
data = sys.stdin.buffer.read(length)
logger.debug("Read data bytes: %r", data)
return data
async def handle_request(self, request_data: bytes) -> Message:
request = messages_pb2.Request()
request.ParseFromString(request_data)
command_name = request.WhichOneof("command")
logger.debug("Command name: %s", command_name)
if not command_name:
logger.error("No command specified")
return self.create_error(
messages_pb2.Error.INVALID_REQUEST, "No command specified"
)
handler = self.handlers.get(command_name)
if not handler:
logger.error("Unknown command: %s", command_name)
return self.create_error(
messages_pb2.Error.INVALID_REQUEST, f"Unknown command: {command_name}"
)
try:
command_message = getattr(request, command_name)
result = await handler(command_message)
return messages_pb2.Response(**{command_name: cast(Any, result)})
except Exception as e:
logger.exception("Error executing command")
return self.create_error(messages_pb2.Error.UNKNOWN, str(e))
def write_message(self, message: Message) -> None:
data = message.SerializeToString()
logger.debug(f"Sending response, length: {len(data)}, data: {data!r}")
length = struct.pack(">I", len(data))
logger.debug(f"Length bytes: {length!r}")
sys.stdout.buffer.write(length)
sys.stdout.buffer.write(data)
sys.stdout.buffer.flush()
def create_error(
self, code: messages_pb2.Error.Code, message: str
) -> messages_pb2.Response:
response = messages_pb2.Response()
response.error.code = code
response.error.message = message
return response
async def main() -> None:
logger.debug("Starting djls-agent...")
try:
logger.debug("Initializing LSPAgent...")
agent = LSPAgent()
logger.debug("Starting LSPAgent serve...")
await agent.serve()
except KeyboardInterrupt:
logger.debug("Received KeyboardInterrupt")
sys.exit(0)
except Exception as e:
logger.exception("Fatal error")
print(f"error: {e}", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
import asyncio
asyncio.run(main())

View file

@ -1,12 +0,0 @@
from __future__ import annotations
import sys
if sys.version_info >= (3, 12):
from typing import override as typing_override
else:
from typing_extensions import (
override as typing_override, # pyright: ignore[reportUnreachable]
)
override = typing_override

View file

@ -1,204 +0,0 @@
from __future__ import annotations
import importlib.metadata
import inspect
import os
import subprocess
import sys
import sysconfig
import traceback
from collections.abc import Awaitable
from collections.abc import Coroutine
from functools import wraps
from typing import Any
from typing import Callable
from typing import TypeVar
from typing import cast
import django
from django.apps import apps
from google.protobuf.message import Message
from .proto.v1 import commands_pb2
from .proto.v1 import django_pb2
from .proto.v1 import messages_pb2
from .proto.v1 import python_pb2
T = TypeVar("T", bound=Message)
R = TypeVar("R", bound=Message)
handlers: dict[str, Callable[[Message], Coroutine[Any, Any, Message]]] = {}
def proto_handler(
request_type: type[T],
error: messages_pb2.Error | None = None,
) -> Callable[
[Callable[[T], R] | Callable[[T], Awaitable[R]]],
Callable[[T], Coroutine[Any, Any, R]],
]:
for req_field in messages_pb2.Request.DESCRIPTOR.fields:
if req_field.message_type == request_type.DESCRIPTOR:
command_name = req_field.name
# Find corresponding response type
for resp_field in messages_pb2.Response.DESCRIPTOR.fields:
if resp_field.name == command_name:
response_type = resp_field.message_type._concrete_class
break
else:
raise ValueError(f"No response type found for {request_type}")
break
else:
raise ValueError(f"Message type {request_type} not found in Request message")
def decorator(
func: Callable[[T], R] | Callable[[T], Awaitable[R]],
) -> Callable[[T], Coroutine[Any, Any, R]]:
is_async = inspect.iscoroutinefunction(func)
@wraps(func)
async def wrapper(request: T) -> R:
try:
if is_async:
result = await cast(Callable[[T], Awaitable[R]], func)(request)
else:
result = cast(Callable[[T], R], func)(request)
# Runtime type checking
if not isinstance(result, response_type):
raise TypeError(
f"Handler returned {type(result)}, expected {response_type}"
)
return result
except Exception as e:
if error:
err = error
else:
err = messages_pb2.Error(
code=messages_pb2.Error.PYTHON_ERROR,
message=str(e),
traceback=traceback.format_exc(),
)
return cast(R, messages_pb2.Response(error=err))
handlers[command_name] = wrapper # pyright: ignore[reportArgumentType]
return wrapper
return decorator
@proto_handler(commands_pb2.Check.HealthRequest)
async def check__health(
_request: commands_pb2.Check.HealthRequest,
) -> commands_pb2.Check.HealthResponse:
return commands_pb2.Check.HealthResponse(passed=True)
@proto_handler(commands_pb2.Check.GeoDjangoPrereqsRequest)
async def check__geodjango_prereqs(
request: commands_pb2.Check.GeoDjangoPrereqsRequest,
) -> commands_pb2.Check.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 commands_pb2.Check.GeoDjangoPrereqsResponse(
passed=(not has_geodjango) or gdal_is_installed
)
@proto_handler(commands_pb2.Python.GetEnvironmentRequest)
async def python__get_environment(
_request: commands_pb2.Python.GetEnvironmentRequest,
) -> commands_pb2.Python.GetEnvironmentResponse:
packages = {}
for dist in importlib.metadata.distributions():
try:
requires = []
try:
requires = list(dist.requires) if hasattr(dist, "requires") else []
except Exception:
pass
location = None
try:
location = str(dist._path) if hasattr(dist, "_path") else None
except Exception:
pass
packages[dist.metadata["Name"]] = python_pb2.Package(
dist_name=dist.metadata["Name"],
dist_version=dist.metadata["Version"],
dist_location=location,
dist_requires=requires,
dist_requires_python=dist.metadata.get("Requires-Python"),
dist_entry_points=str(dist.entry_points)
if hasattr(dist, "entry_points")
else None,
)
except Exception:
continue
sysconfig_paths = sysconfig.get_paths()
version_info = python_pb2.VersionInfo(
major=sys.version_info.major,
minor=sys.version_info.minor,
micro=sys.version_info.micro,
releaselevel={
"alpha": python_pb2.ReleaseLevel.ALPHA,
"beta": python_pb2.ReleaseLevel.BETA,
"candidate": python_pb2.ReleaseLevel.CANDIDATE,
"final": python_pb2.ReleaseLevel.FINAL,
}[sys.version_info.releaselevel],
serial=sys.version_info.serial,
)
return commands_pb2.Python.GetEnvironmentResponse(
python=python_pb2.Python(
os=python_pb2.Os(environ={k: v for k, v in os.environ.items()}),
site=python_pb2.Site(packages=packages),
sys=python_pb2.Sys(
debug_build=hasattr(sys, "gettotalrefcount"),
dev_mode=sys.flags.dev_mode,
is_venv=sys.prefix != sys.base_prefix,
abiflags=sys.abiflags,
base_prefix=sys.base_prefix,
default_encoding=sys.getdefaultencoding(),
executable=sys.executable,
filesystem_encoding=sys.getfilesystemencoding(),
implementation_name=sys.implementation.name,
platform=sys.platform,
prefix=sys.prefix,
builtin_module_names=list(sys.builtin_module_names),
dll_paths=sys.path if sys.platform == "win32" else [],
path=sys.path,
version_info=version_info,
),
sysconfig=python_pb2.Sysconfig(
data=sysconfig_paths.get("data", ""),
include=sysconfig_paths.get("include", ""),
platinclude=sysconfig_paths.get("platinclude", ""),
platlib=sysconfig_paths.get("platlib", ""),
platstdlib=sysconfig_paths.get("platstdlib", ""),
purelib=sysconfig_paths.get("purelib", ""),
scripts=sysconfig_paths.get("scripts", ""),
stdlib=sysconfig_paths.get("stdlib", ""),
),
)
)
@proto_handler(commands_pb2.Django.GetProjectInfoRequest)
async def django__get_project_info(
_request: commands_pb2.Django.GetProjectInfoRequest,
) -> commands_pb2.Django.GetProjectInfoResponse:
return commands_pb2.Django.GetProjectInfoResponse(
project=django_pb2.Project(version=django.__version__)
)

View file

@ -1,44 +0,0 @@
from __future__ import annotations
import logging
import sys
from dataclasses import dataclass
from pathlib import Path
@dataclass
class LogConfig:
log_file: Path | str = "/tmp/djls_debug.log"
log_level: int = logging.DEBUG
console_level: int = logging.DEBUG
file_level: int = logging.DEBUG
format: str = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
def configure_logging(config: LogConfig | None = None) -> logging.Logger:
if config is None:
config = LogConfig()
logger = logging.getLogger("djls")
logger.setLevel(config.log_level)
# Clear any existing handlers
logger.handlers.clear()
# File handler
fh = logging.FileHandler(config.log_file)
fh.setLevel(config.file_level)
# Console handler
ch = logging.StreamHandler(sys.stderr)
ch.setLevel(config.console_level)
# Formatter
formatter = logging.Formatter(config.format)
fh.setFormatter(formatter)
ch.setFormatter(formatter)
logger.addHandler(fh)
logger.addHandler(ch)
return logger

View file

@ -1,4 +0,0 @@
# WARNING: This file is generated by protobuf. DO NOT EDIT!
# Any changes made to this file will be overwritten when the protobuf files are regenerated.
# Source: generated by py-init

View file

@ -1,62 +0,0 @@
# WARNING: This file is generated by protobuf. DO NOT EDIT!
# Any changes made to this file will be overwritten when the protobuf files are regenerated.
# Source: v1/commands.proto
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# NO CHECKED-IN PROTOBUF GENCODE
# source: v1/commands.proto
# Protobuf Python Version: 5.29.1
"""Generated protocol buffer code."""
from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import runtime_version as _runtime_version
from google.protobuf import symbol_database as _symbol_database
from google.protobuf.internal import builder as _builder
_runtime_version.ValidateProtobufRuntimeVersion(
_runtime_version.Domain.PUBLIC,
5,
29,
1,
'',
'v1/commands.proto'
)
# @@protoc_insertion_point(imports)
_sym_db = _symbol_database.Default()
from . import django_pb2 as v1_dot_django__pb2
from . import python_pb2 as v1_dot_python__pb2
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x11v1/commands.proto\x12\x10\x64jls.v1.commands\x1a\x0fv1/django.proto\x1a\x0fv1/python.proto\"\xbd\x01\n\x05\x43heck\x1a\x0f\n\rHealthRequest\x1a>\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\x1a\x19\n\x17GeoDjangoPrereqsRequest\x1aH\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_error\"c\n\x06Python\x1a\x17\n\x15GetEnvironmentRequest\x1a@\n\x16GetEnvironmentResponse\x12&\n\x06python\x18\x01 \x01(\x0b\x32\x16.djls.v1.python.Python\"e\n\x06\x44jango\x1a\x17\n\x15GetProjectInfoRequest\x1a\x42\n\x16GetProjectInfoResponse\x12(\n\x07project\x18\x01 \x01(\x0b\x32\x17.djls.v1.django.Projectb\x06proto3')
_globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'v1.commands_pb2', _globals)
if not _descriptor._USE_C_DESCRIPTORS:
DESCRIPTOR._loaded_options = None
_globals['_CHECK']._serialized_start=74
_globals['_CHECK']._serialized_end=263
_globals['_CHECK_HEALTHREQUEST']._serialized_start=83
_globals['_CHECK_HEALTHREQUEST']._serialized_end=98
_globals['_CHECK_HEALTHRESPONSE']._serialized_start=100
_globals['_CHECK_HEALTHRESPONSE']._serialized_end=162
_globals['_CHECK_GEODJANGOPREREQSREQUEST']._serialized_start=164
_globals['_CHECK_GEODJANGOPREREQSREQUEST']._serialized_end=189
_globals['_CHECK_GEODJANGOPREREQSRESPONSE']._serialized_start=191
_globals['_CHECK_GEODJANGOPREREQSRESPONSE']._serialized_end=263
_globals['_PYTHON']._serialized_start=265
_globals['_PYTHON']._serialized_end=364
_globals['_PYTHON_GETENVIRONMENTREQUEST']._serialized_start=275
_globals['_PYTHON_GETENVIRONMENTREQUEST']._serialized_end=298
_globals['_PYTHON_GETENVIRONMENTRESPONSE']._serialized_start=300
_globals['_PYTHON_GETENVIRONMENTRESPONSE']._serialized_end=364
_globals['_DJANGO']._serialized_start=366
_globals['_DJANGO']._serialized_end=467
_globals['_DJANGO_GETPROJECTINFOREQUEST']._serialized_start=376
_globals['_DJANGO_GETPROJECTINFOREQUEST']._serialized_end=399
_globals['_DJANGO_GETPROJECTINFORESPONSE']._serialized_start=401
_globals['_DJANGO_GETPROJECTINFORESPONSE']._serialized_end=467
# @@protoc_insertion_point(module_scope)

View file

@ -1,59 +0,0 @@
# WARNING: This file is generated by protobuf. DO NOT EDIT!
# Any changes made to this file will be overwritten when the protobuf files are regenerated.
# Source: v1/commands.proto
from . import django_pb2 as _django_pb2
from . import python_pb2 as _python_pb2
from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message
from typing import ClassVar as _ClassVar, Mapping as _Mapping, Optional as _Optional, Union as _Union
DESCRIPTOR: _descriptor.FileDescriptor
class Check(_message.Message):
__slots__ = ()
class HealthRequest(_message.Message):
__slots__ = ()
def __init__(self) -> None: ...
class HealthResponse(_message.Message):
__slots__ = ("passed", "error")
PASSED_FIELD_NUMBER: _ClassVar[int]
ERROR_FIELD_NUMBER: _ClassVar[int]
passed: bool
error: str
def __init__(self, passed: bool = ..., error: _Optional[str] = ...) -> None: ...
class GeoDjangoPrereqsRequest(_message.Message):
__slots__ = ()
def __init__(self) -> None: ...
class GeoDjangoPrereqsResponse(_message.Message):
__slots__ = ("passed", "error")
PASSED_FIELD_NUMBER: _ClassVar[int]
ERROR_FIELD_NUMBER: _ClassVar[int]
passed: bool
error: str
def __init__(self, passed: bool = ..., error: _Optional[str] = ...) -> None: ...
def __init__(self) -> None: ...
class Python(_message.Message):
__slots__ = ()
class GetEnvironmentRequest(_message.Message):
__slots__ = ()
def __init__(self) -> None: ...
class GetEnvironmentResponse(_message.Message):
__slots__ = ("python",)
PYTHON_FIELD_NUMBER: _ClassVar[int]
python: _python_pb2.Python
def __init__(self, python: _Optional[_Union[_python_pb2.Python, _Mapping]] = ...) -> None: ...
def __init__(self) -> None: ...
class Django(_message.Message):
__slots__ = ()
class GetProjectInfoRequest(_message.Message):
__slots__ = ()
def __init__(self) -> None: ...
class GetProjectInfoResponse(_message.Message):
__slots__ = ("project",)
PROJECT_FIELD_NUMBER: _ClassVar[int]
project: _django_pb2.Project
def __init__(self, project: _Optional[_Union[_django_pb2.Project, _Mapping]] = ...) -> None: ...
def __init__(self) -> None: ...

View file

@ -1,40 +0,0 @@
# WARNING: This file is generated by protobuf. DO NOT EDIT!
# Any changes made to this file will be overwritten when the protobuf files are regenerated.
# Source: v1/django.proto
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# NO CHECKED-IN PROTOBUF GENCODE
# source: v1/django.proto
# Protobuf Python Version: 5.29.1
"""Generated protocol buffer code."""
from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import runtime_version as _runtime_version
from google.protobuf import symbol_database as _symbol_database
from google.protobuf.internal import builder as _builder
_runtime_version.ValidateProtobufRuntimeVersion(
_runtime_version.Domain.PUBLIC,
5,
29,
1,
'',
'v1/django.proto'
)
# @@protoc_insertion_point(imports)
_sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0fv1/django.proto\x12\x0e\x64jls.v1.django\"\x1a\n\x07Project\x12\x0f\n\x07version\x18\x03 \x01(\tb\x06proto3')
_globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'v1.django_pb2', _globals)
if not _descriptor._USE_C_DESCRIPTORS:
DESCRIPTOR._loaded_options = None
_globals['_PROJECT']._serialized_start=35
_globals['_PROJECT']._serialized_end=61
# @@protoc_insertion_point(module_scope)

View file

@ -1,15 +0,0 @@
# WARNING: This file is generated by protobuf. DO NOT EDIT!
# Any changes made to this file will be overwritten when the protobuf files are regenerated.
# Source: v1/django.proto
from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message
from typing import ClassVar as _ClassVar, Optional as _Optional
DESCRIPTOR: _descriptor.FileDescriptor
class Project(_message.Message):
__slots__ = ("version",)
VERSION_FIELD_NUMBER: _ClassVar[int]
version: str
def __init__(self, version: _Optional[str] = ...) -> None: ...

View file

@ -1,47 +0,0 @@
# WARNING: This file is generated by protobuf. DO NOT EDIT!
# Any changes made to this file will be overwritten when the protobuf files are regenerated.
# Source: v1/messages.proto
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# NO CHECKED-IN PROTOBUF GENCODE
# source: v1/messages.proto
# Protobuf Python Version: 5.29.1
"""Generated protocol buffer code."""
from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import runtime_version as _runtime_version
from google.protobuf import symbol_database as _symbol_database
from google.protobuf.internal import builder as _builder
_runtime_version.ValidateProtobufRuntimeVersion(
_runtime_version.Domain.PUBLIC,
5,
29,
1,
'',
'v1/messages.proto'
)
# @@protoc_insertion_point(imports)
_sym_db = _symbol_database.Default()
from . import commands_pb2 as v1_dot_commands__pb2
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x11v1/messages.proto\x12\x10\x64jls.v1.messages\x1a\x11v1/commands.proto\"\xd2\x02\n\x07Request\x12>\n\rcheck__health\x18\x01 \x01(\x0b\x32%.djls.v1.commands.Check.HealthRequestH\x00\x12S\n\x18\x63heck__geodjango_prereqs\x18\x02 \x01(\x0b\x32/.djls.v1.commands.Check.GeoDjangoPrereqsRequestH\x00\x12R\n\x17python__get_environment\x18\xe8\x07 \x01(\x0b\x32..djls.v1.commands.Python.GetEnvironmentRequestH\x00\x12S\n\x18\x64jango__get_project_info\x18\xd0\x0f \x01(\x0b\x32..djls.v1.commands.Django.GetProjectInfoRequestH\x00\x42\t\n\x07\x63ommand\"\x81\x03\n\x08Response\x12?\n\rcheck__health\x18\x01 \x01(\x0b\x32&.djls.v1.commands.Check.HealthResponseH\x00\x12T\n\x18\x63heck__geodjango_prereqs\x18\x02 \x01(\x0b\x32\x30.djls.v1.commands.Check.GeoDjangoPrereqsResponseH\x00\x12S\n\x17python__get_environment\x18\xe8\x07 \x01(\x0b\x32/.djls.v1.commands.Python.GetEnvironmentResponseH\x00\x12T\n\x18\x64jango__get_project_info\x18\xd0\x0f \x01(\x0b\x32/.djls.v1.commands.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)
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'v1.messages_pb2', _globals)
if not _descriptor._USE_C_DESCRIPTORS:
DESCRIPTOR._loaded_options = None
_globals['_REQUEST']._serialized_start=59
_globals['_REQUEST']._serialized_end=397
_globals['_RESPONSE']._serialized_start=400
_globals['_RESPONSE']._serialized_end=785
_globals['_ERROR']._serialized_start=788
_globals['_ERROR']._serialized_end=953
_globals['_ERROR_CODE']._serialized_start=877
_globals['_ERROR_CODE']._serialized_end=953
# @@protoc_insertion_point(module_scope)

View file

@ -1,57 +0,0 @@
# WARNING: This file is generated by protobuf. DO NOT EDIT!
# Any changes made to this file will be overwritten when the protobuf files are regenerated.
# Source: v1/messages.proto
from . import commands_pb2 as _commands_pb2
from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper
from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message
from typing import ClassVar as _ClassVar, Mapping as _Mapping, Optional as _Optional, Union as _Union
DESCRIPTOR: _descriptor.FileDescriptor
class Request(_message.Message):
__slots__ = ("check__health", "check__geodjango_prereqs", "python__get_environment", "django__get_project_info")
CHECK__HEALTH_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: _commands_pb2.Check.HealthRequest
check__geodjango_prereqs: _commands_pb2.Check.GeoDjangoPrereqsRequest
python__get_environment: _commands_pb2.Python.GetEnvironmentRequest
django__get_project_info: _commands_pb2.Django.GetProjectInfoRequest
def __init__(self, check__health: _Optional[_Union[_commands_pb2.Check.HealthRequest, _Mapping]] = ..., check__geodjango_prereqs: _Optional[_Union[_commands_pb2.Check.GeoDjangoPrereqsRequest, _Mapping]] = ..., python__get_environment: _Optional[_Union[_commands_pb2.Python.GetEnvironmentRequest, _Mapping]] = ..., django__get_project_info: _Optional[_Union[_commands_pb2.Django.GetProjectInfoRequest, _Mapping]] = ...) -> None: ...
class Response(_message.Message):
__slots__ = ("check__health", "check__geodjango_prereqs", "python__get_environment", "django__get_project_info", "error")
CHECK__HEALTH_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: _commands_pb2.Check.HealthResponse
check__geodjango_prereqs: _commands_pb2.Check.GeoDjangoPrereqsResponse
python__get_environment: _commands_pb2.Python.GetEnvironmentResponse
django__get_project_info: _commands_pb2.Django.GetProjectInfoResponse
error: Error
def __init__(self, check__health: _Optional[_Union[_commands_pb2.Check.HealthResponse, _Mapping]] = ..., check__geodjango_prereqs: _Optional[_Union[_commands_pb2.Check.GeoDjangoPrereqsResponse, _Mapping]] = ..., python__get_environment: _Optional[_Union[_commands_pb2.Python.GetEnvironmentResponse, _Mapping]] = ..., django__get_project_info: _Optional[_Union[_commands_pb2.Django.GetProjectInfoResponse, _Mapping]] = ..., error: _Optional[_Union[Error, _Mapping]] = ...) -> None: ...
class Error(_message.Message):
__slots__ = ("code", "message", "traceback")
class Code(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
__slots__ = ()
UNKNOWN: _ClassVar[Error.Code]
INVALID_REQUEST: _ClassVar[Error.Code]
PYTHON_ERROR: _ClassVar[Error.Code]
DJANGO_ERROR: _ClassVar[Error.Code]
UNKNOWN: Error.Code
INVALID_REQUEST: Error.Code
PYTHON_ERROR: Error.Code
DJANGO_ERROR: Error.Code
CODE_FIELD_NUMBER: _ClassVar[int]
MESSAGE_FIELD_NUMBER: _ClassVar[int]
TRACEBACK_FIELD_NUMBER: _ClassVar[int]
code: Error.Code
message: str
traceback: str
def __init__(self, code: _Optional[_Union[Error.Code, str]] = ..., message: _Optional[str] = ..., traceback: _Optional[str] = ...) -> None: ...

View file

@ -1,62 +0,0 @@
# WARNING: This file is generated by protobuf. DO NOT EDIT!
# Any changes made to this file will be overwritten when the protobuf files are regenerated.
# Source: v1/python.proto
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# NO CHECKED-IN PROTOBUF GENCODE
# source: v1/python.proto
# Protobuf Python Version: 5.29.1
"""Generated protocol buffer code."""
from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import runtime_version as _runtime_version
from google.protobuf import symbol_database as _symbol_database
from google.protobuf.internal import builder as _builder
_runtime_version.ValidateProtobufRuntimeVersion(
_runtime_version.Domain.PUBLIC,
5,
29,
1,
'',
'v1/python.proto'
)
# @@protoc_insertion_point(imports)
_sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0fv1/python.proto\x12\x0e\x64jls.v1.python\"\x9c\x01\n\x06Python\x12\x1e\n\x02os\x18\x01 \x01(\x0b\x32\x12.djls.v1.python.Os\x12\"\n\x04site\x18\x02 \x01(\x0b\x32\x14.djls.v1.python.Site\x12 \n\x03sys\x18\x03 \x01(\x0b\x32\x13.djls.v1.python.Sys\x12,\n\tsysconfig\x18\x04 \x01(\x0b\x32\x19.djls.v1.python.Sysconfig\"f\n\x02Os\x12\x30\n\x07\x65nviron\x18\x01 \x03(\x0b\x32\x1f.djls.v1.python.Os.EnvironEntry\x1a.\n\x0c\x45nvironEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\x86\x01\n\x04Site\x12\x34\n\x08packages\x18\x01 \x03(\x0b\x32\".djls.v1.python.Site.PackagesEntry\x1aH\n\rPackagesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12&\n\x05value\x18\x02 \x01(\x0b\x32\x17.djls.v1.python.Package:\x02\x38\x01\"\xe0\x02\n\x03Sys\x12\x13\n\x0b\x64\x65\x62ug_build\x18\x01 \x01(\x08\x12\x10\n\x08\x64\x65v_mode\x18\x02 \x01(\x08\x12\x0f\n\x07is_venv\x18\x03 \x01(\x08\x12\x10\n\x08\x61\x62iflags\x18\x04 \x01(\t\x12\x13\n\x0b\x62\x61se_prefix\x18\x05 \x01(\t\x12\x18\n\x10\x64\x65\x66\x61ult_encoding\x18\x06 \x01(\t\x12\x12\n\nexecutable\x18\x07 \x01(\t\x12\x1b\n\x13\x66ilesystem_encoding\x18\x08 \x01(\t\x12\x1b\n\x13implementation_name\x18\t \x01(\t\x12\x10\n\x08platform\x18\n \x01(\t\x12\x0e\n\x06prefix\x18\x0b \x01(\t\x12\x1c\n\x14\x62uiltin_module_names\x18\x0c \x03(\t\x12\x11\n\tdll_paths\x18\r \x03(\t\x12\x0c\n\x04path\x18\x0e \x03(\t\x12\x31\n\x0cversion_info\x18\x0f \x01(\x0b\x32\x1b.djls.v1.python.VersionInfo\"~\n\x0bVersionInfo\x12\r\n\x05major\x18\x01 \x01(\r\x12\r\n\x05minor\x18\x02 \x01(\r\x12\r\n\x05micro\x18\x03 \x01(\r\x12\x32\n\x0creleaselevel\x18\x04 \x01(\x0e\x32\x1c.djls.v1.python.ReleaseLevel\x12\x0e\n\x06serial\x18\x05 \x01(\r\"\x96\x01\n\tSysconfig\x12\x0c\n\x04\x64\x61ta\x18\x01 \x01(\t\x12\x0f\n\x07include\x18\x02 \x01(\t\x12\x13\n\x0bplatinclude\x18\x03 \x01(\t\x12\x0f\n\x07platlib\x18\x04 \x01(\t\x12\x12\n\nplatstdlib\x18\x05 \x01(\t\x12\x0f\n\x07purelib\x18\x06 \x01(\t\x12\x0f\n\x07scripts\x18\x07 \x01(\t\x12\x0e\n\x06stdlib\x18\x08 \x01(\t\"\x97\x02\n\x07Package\x12\x11\n\tdist_name\x18\x01 \x01(\t\x12\x14\n\x0c\x64ist_version\x18\x02 \x01(\t\x12\x1a\n\rdist_editable\x18\x03 \x01(\x08H\x00\x88\x01\x01\x12\x1e\n\x11\x64ist_entry_points\x18\x04 \x01(\tH\x01\x88\x01\x01\x12\x1a\n\rdist_location\x18\x05 \x01(\tH\x02\x88\x01\x01\x12\x15\n\rdist_requires\x18\x06 \x03(\t\x12!\n\x14\x64ist_requires_python\x18\x07 \x01(\tH\x03\x88\x01\x01\x42\x10\n\x0e_dist_editableB\x14\n\x12_dist_entry_pointsB\x10\n\x0e_dist_locationB\x17\n\x15_dist_requires_python*=\n\x0cReleaseLevel\x12\t\n\x05\x41LPHA\x10\x00\x12\x08\n\x04\x42\x45TA\x10\x01\x12\r\n\tCANDIDATE\x10\x02\x12\t\n\x05\x46INAL\x10\x03\x62\x06proto3')
_globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'v1.python_pb2', _globals)
if not _descriptor._USE_C_DESCRIPTORS:
DESCRIPTOR._loaded_options = None
_globals['_OS_ENVIRONENTRY']._loaded_options = None
_globals['_OS_ENVIRONENTRY']._serialized_options = b'8\001'
_globals['_SITE_PACKAGESENTRY']._loaded_options = None
_globals['_SITE_PACKAGESENTRY']._serialized_options = b'8\001'
_globals['_RELEASELEVEL']._serialized_start=1353
_globals['_RELEASELEVEL']._serialized_end=1414
_globals['_PYTHON']._serialized_start=36
_globals['_PYTHON']._serialized_end=192
_globals['_OS']._serialized_start=194
_globals['_OS']._serialized_end=296
_globals['_OS_ENVIRONENTRY']._serialized_start=250
_globals['_OS_ENVIRONENTRY']._serialized_end=296
_globals['_SITE']._serialized_start=299
_globals['_SITE']._serialized_end=433
_globals['_SITE_PACKAGESENTRY']._serialized_start=361
_globals['_SITE_PACKAGESENTRY']._serialized_end=433
_globals['_SYS']._serialized_start=436
_globals['_SYS']._serialized_end=788
_globals['_VERSIONINFO']._serialized_start=790
_globals['_VERSIONINFO']._serialized_end=916
_globals['_SYSCONFIG']._serialized_start=919
_globals['_SYSCONFIG']._serialized_end=1069
_globals['_PACKAGE']._serialized_start=1072
_globals['_PACKAGE']._serialized_end=1351
# @@protoc_insertion_point(module_scope)

View file

@ -1,146 +0,0 @@
# WARNING: This file is generated by protobuf. DO NOT EDIT!
# Any changes made to this file will be overwritten when the protobuf files are regenerated.
# Source: v1/python.proto
from google.protobuf.internal import containers as _containers
from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper
from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message
from typing import ClassVar as _ClassVar, Iterable as _Iterable, Mapping as _Mapping, Optional as _Optional, Union as _Union
DESCRIPTOR: _descriptor.FileDescriptor
class ReleaseLevel(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
__slots__ = ()
ALPHA: _ClassVar[ReleaseLevel]
BETA: _ClassVar[ReleaseLevel]
CANDIDATE: _ClassVar[ReleaseLevel]
FINAL: _ClassVar[ReleaseLevel]
ALPHA: ReleaseLevel
BETA: ReleaseLevel
CANDIDATE: ReleaseLevel
FINAL: ReleaseLevel
class Python(_message.Message):
__slots__ = ("os", "site", "sys", "sysconfig")
OS_FIELD_NUMBER: _ClassVar[int]
SITE_FIELD_NUMBER: _ClassVar[int]
SYS_FIELD_NUMBER: _ClassVar[int]
SYSCONFIG_FIELD_NUMBER: _ClassVar[int]
os: Os
site: Site
sys: Sys
sysconfig: Sysconfig
def __init__(self, os: _Optional[_Union[Os, _Mapping]] = ..., site: _Optional[_Union[Site, _Mapping]] = ..., sys: _Optional[_Union[Sys, _Mapping]] = ..., sysconfig: _Optional[_Union[Sysconfig, _Mapping]] = ...) -> None: ...
class Os(_message.Message):
__slots__ = ("environ",)
class EnvironEntry(_message.Message):
__slots__ = ("key", "value")
KEY_FIELD_NUMBER: _ClassVar[int]
VALUE_FIELD_NUMBER: _ClassVar[int]
key: str
value: str
def __init__(self, key: _Optional[str] = ..., value: _Optional[str] = ...) -> None: ...
ENVIRON_FIELD_NUMBER: _ClassVar[int]
environ: _containers.ScalarMap[str, str]
def __init__(self, environ: _Optional[_Mapping[str, str]] = ...) -> None: ...
class Site(_message.Message):
__slots__ = ("packages",)
class PackagesEntry(_message.Message):
__slots__ = ("key", "value")
KEY_FIELD_NUMBER: _ClassVar[int]
VALUE_FIELD_NUMBER: _ClassVar[int]
key: str
value: Package
def __init__(self, key: _Optional[str] = ..., value: _Optional[_Union[Package, _Mapping]] = ...) -> None: ...
PACKAGES_FIELD_NUMBER: _ClassVar[int]
packages: _containers.MessageMap[str, Package]
def __init__(self, packages: _Optional[_Mapping[str, Package]] = ...) -> None: ...
class Sys(_message.Message):
__slots__ = ("debug_build", "dev_mode", "is_venv", "abiflags", "base_prefix", "default_encoding", "executable", "filesystem_encoding", "implementation_name", "platform", "prefix", "builtin_module_names", "dll_paths", "path", "version_info")
DEBUG_BUILD_FIELD_NUMBER: _ClassVar[int]
DEV_MODE_FIELD_NUMBER: _ClassVar[int]
IS_VENV_FIELD_NUMBER: _ClassVar[int]
ABIFLAGS_FIELD_NUMBER: _ClassVar[int]
BASE_PREFIX_FIELD_NUMBER: _ClassVar[int]
DEFAULT_ENCODING_FIELD_NUMBER: _ClassVar[int]
EXECUTABLE_FIELD_NUMBER: _ClassVar[int]
FILESYSTEM_ENCODING_FIELD_NUMBER: _ClassVar[int]
IMPLEMENTATION_NAME_FIELD_NUMBER: _ClassVar[int]
PLATFORM_FIELD_NUMBER: _ClassVar[int]
PREFIX_FIELD_NUMBER: _ClassVar[int]
BUILTIN_MODULE_NAMES_FIELD_NUMBER: _ClassVar[int]
DLL_PATHS_FIELD_NUMBER: _ClassVar[int]
PATH_FIELD_NUMBER: _ClassVar[int]
VERSION_INFO_FIELD_NUMBER: _ClassVar[int]
debug_build: bool
dev_mode: bool
is_venv: bool
abiflags: str
base_prefix: str
default_encoding: str
executable: str
filesystem_encoding: str
implementation_name: str
platform: str
prefix: str
builtin_module_names: _containers.RepeatedScalarFieldContainer[str]
dll_paths: _containers.RepeatedScalarFieldContainer[str]
path: _containers.RepeatedScalarFieldContainer[str]
version_info: VersionInfo
def __init__(self, debug_build: bool = ..., dev_mode: bool = ..., is_venv: bool = ..., abiflags: _Optional[str] = ..., base_prefix: _Optional[str] = ..., default_encoding: _Optional[str] = ..., executable: _Optional[str] = ..., filesystem_encoding: _Optional[str] = ..., implementation_name: _Optional[str] = ..., platform: _Optional[str] = ..., prefix: _Optional[str] = ..., builtin_module_names: _Optional[_Iterable[str]] = ..., dll_paths: _Optional[_Iterable[str]] = ..., path: _Optional[_Iterable[str]] = ..., version_info: _Optional[_Union[VersionInfo, _Mapping]] = ...) -> None: ...
class VersionInfo(_message.Message):
__slots__ = ("major", "minor", "micro", "releaselevel", "serial")
MAJOR_FIELD_NUMBER: _ClassVar[int]
MINOR_FIELD_NUMBER: _ClassVar[int]
MICRO_FIELD_NUMBER: _ClassVar[int]
RELEASELEVEL_FIELD_NUMBER: _ClassVar[int]
SERIAL_FIELD_NUMBER: _ClassVar[int]
major: int
minor: int
micro: int
releaselevel: ReleaseLevel
serial: int
def __init__(self, major: _Optional[int] = ..., minor: _Optional[int] = ..., micro: _Optional[int] = ..., releaselevel: _Optional[_Union[ReleaseLevel, str]] = ..., serial: _Optional[int] = ...) -> None: ...
class Sysconfig(_message.Message):
__slots__ = ("data", "include", "platinclude", "platlib", "platstdlib", "purelib", "scripts", "stdlib")
DATA_FIELD_NUMBER: _ClassVar[int]
INCLUDE_FIELD_NUMBER: _ClassVar[int]
PLATINCLUDE_FIELD_NUMBER: _ClassVar[int]
PLATLIB_FIELD_NUMBER: _ClassVar[int]
PLATSTDLIB_FIELD_NUMBER: _ClassVar[int]
PURELIB_FIELD_NUMBER: _ClassVar[int]
SCRIPTS_FIELD_NUMBER: _ClassVar[int]
STDLIB_FIELD_NUMBER: _ClassVar[int]
data: str
include: str
platinclude: str
platlib: str
platstdlib: str
purelib: str
scripts: str
stdlib: str
def __init__(self, data: _Optional[str] = ..., include: _Optional[str] = ..., platinclude: _Optional[str] = ..., platlib: _Optional[str] = ..., platstdlib: _Optional[str] = ..., purelib: _Optional[str] = ..., scripts: _Optional[str] = ..., stdlib: _Optional[str] = ...) -> None: ...
class Package(_message.Message):
__slots__ = ("dist_name", "dist_version", "dist_editable", "dist_entry_points", "dist_location", "dist_requires", "dist_requires_python")
DIST_NAME_FIELD_NUMBER: _ClassVar[int]
DIST_VERSION_FIELD_NUMBER: _ClassVar[int]
DIST_EDITABLE_FIELD_NUMBER: _ClassVar[int]
DIST_ENTRY_POINTS_FIELD_NUMBER: _ClassVar[int]
DIST_LOCATION_FIELD_NUMBER: _ClassVar[int]
DIST_REQUIRES_FIELD_NUMBER: _ClassVar[int]
DIST_REQUIRES_PYTHON_FIELD_NUMBER: _ClassVar[int]
dist_name: str
dist_version: str
dist_editable: bool
dist_entry_points: str
dist_location: str
dist_requires: _containers.RepeatedScalarFieldContainer[str]
dist_requires_python: str
def __init__(self, dist_name: _Optional[str] = ..., dist_version: _Optional[str] = ..., dist_editable: bool = ..., dist_entry_points: _Optional[str] = ..., dist_location: _Optional[str] = ..., dist_requires: _Optional[_Iterable[str]] = ..., dist_requires_python: _Optional[str] = ...) -> None: ...

View file

@ -1 +0,0 @@
../../LICENSE

View file

@ -1 +0,0 @@
# djls-server

View file

@ -1 +0,0 @@
../../crates

View file

@ -1 +0,0 @@
../../proto

View file

@ -1,38 +0,0 @@
[build-system]
requires = ["maturin>=1.0,<2.0"]
build-backend = "maturin"
[project]
name = "djls-server"
version = "5.1.0a0"
description = "Binary distribution package for the Django Language Server"
readme = "README.md"
authors = [
{ name = "Josh Thomas", email = "josh@joshthomas.dev" }
]
requires-python = ">=3.9"
dependencies = []
classifiers = [
"Development Status :: 3 - Alpha",
"Framework :: Django",
"Framework :: Django :: 4.2",
"Framework :: Django :: 5.0",
"Framework :: Django :: 5.1",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
"Programming Language :: Rust",
"Topic :: Software Development",
"Topic :: Text Editors :: Integrated Development Environments (IDE)"
]
[tool.maturin]
bindings = "bin"
manifest-path = "crates/djls/Cargo.toml"
module-name = "djls"
strip = true
include = [
{ path = "proto/**/*", format = ["sdist", "wheel"] },
{ path = "LICENSE", format = "sdist" },
{ path = "rust-toolchain.toml", format = ["sdist", "wheel"] },
]

View file

@ -1 +0,0 @@
../../rust-toolchain.toml

View file

@ -1,35 +0,0 @@
syntax = "proto3";
package djls.v1.commands;
import "v1/django.proto";
import "v1/python.proto";
message Check {
message HealthRequest {}
message HealthResponse {
bool passed = 1;
optional string error = 2;
}
message GeoDjangoPrereqsRequest {}
message GeoDjangoPrereqsResponse {
bool passed = 1;
optional string error = 2;
}
}
message Python {
message GetEnvironmentRequest {}
message GetEnvironmentResponse {
python.Python python = 1;
}
}
message Django {
message GetProjectInfoRequest {}
message GetProjectInfoResponse {
django.Project project = 1;
}
}

View file

@ -1,7 +0,0 @@
syntax = "proto3";
package djls.v1.django;
message Project {
string version = 3;
}

View file

@ -1,36 +0,0 @@
syntax = "proto3";
package djls.v1.messages;
import "v1/commands.proto";
message Request {
oneof command {
commands.Check.HealthRequest check__health = 1;
commands.Check.GeoDjangoPrereqsRequest check__geodjango_prereqs = 2;
commands.Python.GetEnvironmentRequest python__get_environment = 1000;
commands.Django.GetProjectInfoRequest django__get_project_info = 2000;
}
}
message Response {
oneof result {
commands.Check.HealthResponse check__health = 1;
commands.Check.GeoDjangoPrereqsResponse check__geodjango_prereqs = 2;
commands.Python.GetEnvironmentResponse python__get_environment = 1000;
commands.Django.GetProjectInfoResponse django__get_project_info = 2000;
Error error = 9000;
}
}
message Error {
Code code = 1;
string message = 2;
string traceback = 3;
enum Code {
UNKNOWN = 0;
INVALID_REQUEST = 1;
PYTHON_ERROR = 2;
DJANGO_ERROR = 3;
}
}

View file

@ -1,73 +0,0 @@
syntax = "proto3";
package djls.v1.python;
// models
message Python {
Os os = 1;
Site site = 2;
Sys sys = 3;
Sysconfig sysconfig = 4;
}
message Os {
map<string, string> environ = 1;
}
message Site {
map<string, Package> packages = 1;
}
message Sys {
bool debug_build = 1;
bool dev_mode = 2;
bool is_venv = 3;
string abiflags = 4;
string base_prefix = 5;
string default_encoding = 6;
string executable = 7;
string filesystem_encoding = 8;
string implementation_name = 9;
string platform = 10;
string prefix = 11;
repeated string builtin_module_names = 12;
repeated string dll_paths = 13;
repeated string path = 14;
VersionInfo version_info = 15;
}
message VersionInfo {
uint32 major = 1;
uint32 minor = 2;
uint32 micro = 3;
ReleaseLevel releaselevel = 4;
uint32 serial = 5;
}
enum ReleaseLevel {
ALPHA = 0;
BETA = 1;
CANDIDATE = 2;
FINAL = 3;
}
message Sysconfig {
string data = 1;
string include = 2;
string platinclude = 3;
string platlib = 4;
string platstdlib = 5;
string purelib = 6;
string scripts = 7;
string stdlib = 8;
}
message Package {
string dist_name = 1;
string dist_version = 2;
optional bool dist_editable = 3;
optional string dist_entry_points = 4;
optional string dist_location = 5;
repeated string dist_requires = 6;
optional string dist_requires_python = 7;
}

View file

@ -1,6 +1,6 @@
[build-system]
build-backend = "hatchling.build"
requires = ["hatchling"]
requires = ["maturin>=1.0,<2.0"]
build-backend = "maturin"
[dependency-groups]
dev = [
@ -20,9 +20,6 @@ authors = [
{ name = "Josh Thomas", email = "josh@joshthomas.dev" }
]
requires-python = ">=3.9"
dependencies = [
"djls-agent"
]
classifiers = [
"Development Status :: 3 - Alpha",
"Framework :: Django",
@ -46,8 +43,8 @@ classifiers = [
"Topic :: Text Editors :: Integrated Development Environments (IDE)"
]
[project.optional-dependencies]
server = ["djls-server"]
[project.scripts]
djls = "djls:cli_entrypoint"
[tool.bumpver]
commit = true
@ -61,19 +58,18 @@ version_pattern = "MAJOR.MINOR.PATCH[-TAG[.NUM]]"
"crates/djls/Cargo.toml" = [
'version = "{version}"',
]
"packages/djls-agent/pyproject.toml" = [
'version = "{pep440_version}"',
]
"packages/djls-server/pyproject.toml" = [
'version = "{pep440_version}"',
]
"pyproject.toml" = [
'version = "{pep440_version}"',
'current_version = "{version}"',
]
[tool.hatch.build]
packages = [".uv-workspace/"]
[tool.maturin]
manifest-path = "crates/djls/Cargo.toml"
strip = true
include = [
{ path = "LICENSE", format = "sdist" },
{ path = "rust-toolchain.toml", format = ["sdist", "wheel"] },
]
[tool.ruff]
# Exclude a variety of commonly ignored directories.
@ -129,7 +125,7 @@ unfixable = []
[tool.ruff.lint.isort]
force-single-line = true
known-first-party = ["djls_agent"]
known-first-party = ["djls"]
required-imports = ["from __future__ import annotations"]
[tool.ruff.lint.per-file-ignores]
@ -139,10 +135,3 @@ required-imports = ["from __future__ import annotations"]
[tool.ruff.lint.pyupgrade]
# Preserve types, even if a file imports `from __future__ import annotations`.
keep-runtime-typing = true
[tool.uv.sources]
djls-agent = { workspace = true }
djls-server = { workspace = true }
[tool.uv.workspace]
members = ["packages/*"]

67
uv.lock generated
View file

@ -1,13 +1,6 @@
version = 1
requires-python = ">=3.9"
[manifest]
members = [
"djls",
"djls-agent",
"djls-server",
]
[[package]]
name = "asgiref"
version = "3.8.1"
@ -191,14 +184,6 @@ wheels = [
name = "djls"
version = "5.1.0a0"
source = { editable = "." }
dependencies = [
{ name = "djls-agent" },
]
[package.optional-dependencies]
server = [
{ name = "djls-server" },
]
[package.dev-dependencies]
dev = [
@ -210,10 +195,6 @@ docs = [
]
[package.metadata]
requires-dist = [
{ name = "djls-agent", editable = "packages/djls-agent" },
{ name = "djls-server", marker = "extra == 'server'", editable = "packages/djls-server" },
]
[package.metadata.requires-dev]
dev = [
@ -222,38 +203,6 @@ dev = [
]
docs = [{ name = "mkdocs-material", specifier = ">=9.5.49" }]
[[package]]
name = "djls-agent"
version = "5.1.0a0"
source = { editable = "packages/djls-agent" }
dependencies = [
{ name = "django" },
{ name = "protobuf" },
]
[package.dev-dependencies]
dev = [
{ name = "django-stubs" },
{ name = "ruff" },
]
[package.metadata]
requires-dist = [
{ name = "django", specifier = ">=4.2" },
{ name = "protobuf", specifier = ">=5.29.1" },
]
[package.metadata.requires-dev]
dev = [
{ name = "django-stubs", specifier = ">=5.1.1" },
{ name = "ruff", specifier = ">=0.8.2" },
]
[[package]]
name = "djls-server"
version = "5.1.0a0"
source = { editable = "packages/djls-server" }
[[package]]
name = "ghp-import"
version = "2.1.0"
@ -495,22 +444,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/3c/a6/bc1012356d8ece4d66dd75c4b9fc6c1f6650ddd5991e421177d9f8f671be/platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb", size = 18439 },
]
[[package]]
name = "protobuf"
version = "5.29.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/d2/4f/1639b7b1633d8fd55f216ba01e21bf2c43384ab25ef3ddb35d85a52033e8/protobuf-5.29.1.tar.gz", hash = "sha256:683be02ca21a6ffe80db6dd02c0b5b2892322c59ca57fd6c872d652cb80549cb", size = 424965 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/50/c7/28669b04691a376cf7d0617d612f126aa0fff763d57df0142f9bf474c5b8/protobuf-5.29.1-cp310-abi3-win32.whl", hash = "sha256:22c1f539024241ee545cbcb00ee160ad1877975690b16656ff87dde107b5f110", size = 422706 },
{ url = "https://files.pythonhosted.org/packages/e3/33/dc7a7712f457456b7e0b16420ab8ba1cc8686751d3f28392eb43d0029ab9/protobuf-5.29.1-cp310-abi3-win_amd64.whl", hash = "sha256:1fc55267f086dd4050d18ef839d7bd69300d0d08c2a53ca7df3920cc271a3c34", size = 434505 },
{ url = "https://files.pythonhosted.org/packages/e5/39/44239fb1c6ec557e1731d996a5de89a9eb1ada7a92491fcf9c5d714052ed/protobuf-5.29.1-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:d473655e29c0c4bbf8b69e9a8fb54645bc289dead6d753b952e7aa660254ae18", size = 417822 },
{ url = "https://files.pythonhosted.org/packages/fb/4a/ec56f101d38d4bef2959a9750209809242d86cf8b897db00f2f98bfa360e/protobuf-5.29.1-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:b5ba1d0e4c8a40ae0496d0e2ecfdbb82e1776928a205106d14ad6985a09ec155", size = 319572 },
{ url = "https://files.pythonhosted.org/packages/04/52/c97c58a33b3d6c89a8138788576d372a90a6556f354799971c6b4d16d871/protobuf-5.29.1-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:8ee1461b3af56145aca2800e6a3e2f928108c749ba8feccc6f5dd0062c410c0d", size = 319671 },
{ url = "https://files.pythonhosted.org/packages/99/19/5a3957e08de18578131810563ccfeebc7d2aad31ee52e367a61f56cc3cab/protobuf-5.29.1-cp39-cp39-win32.whl", hash = "sha256:5a41deccfa5e745cef5c65a560c76ec0ed8e70908a67cc8f4da5fce588b50d57", size = 422671 },
{ url = "https://files.pythonhosted.org/packages/24/67/8bc07bb755c8badf08db4a8bc2eb542a4e733135a6d584d1922b701d7751/protobuf-5.29.1-cp39-cp39-win_amd64.whl", hash = "sha256:012ce28d862ff417fd629285aca5d9772807f15ceb1a0dbd15b88f58c776c98c", size = 434591 },
{ url = "https://files.pythonhosted.org/packages/3b/24/c8c49df8f6587719e1d400109b16c10c6902d0c9adddc8fff82840146f99/protobuf-5.29.1-py3-none-any.whl", hash = "sha256:32600ddb9c2a53dedc25b8581ea0f1fd8ea04956373c0c07577ce58d312522e0", size = 172547 },
]
[[package]]
name = "pygments"
version = "2.18.0"