mirror of
https://github.com/joshuadavidthomas/django-language-server.git
synced 2025-07-09 21:54:59 +00:00
swap from IPC architecture to PyO3 library (#45)
This commit is contained in:
parent
df30aafde5
commit
a73e912e0f
53 changed files with 136 additions and 2224 deletions
|
@ -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"
|
||||
|
|
|
@ -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 }
|
|
@ -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(())
|
||||
}
|
||||
}
|
|
@ -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),
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
mod apps;
|
||||
mod django;
|
||||
mod templates;
|
||||
|
||||
pub use django::DjangoProject;
|
|
@ -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(())
|
||||
}
|
||||
}
|
|
@ -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"
|
|
@ -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();
|
||||
}
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
|
@ -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),
|
||||
}
|
|
@ -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"));
|
||||
}
|
||||
}
|
|
@ -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),
|
||||
}
|
47
crates/djls-ipc/tests/fixtures/echo_server.py
vendored
47
crates/djls-ipc/tests/fixtures/echo_server.py
vendored
|
@ -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))
|
|
@ -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"
|
|
@ -1,6 +0,0 @@
|
|||
mod packaging;
|
||||
mod python;
|
||||
|
||||
pub use crate::packaging::PackagingError;
|
||||
pub use crate::python::Python;
|
||||
pub use crate::python::PythonError;
|
|
@ -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),
|
||||
}
|
|
@ -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),
|
||||
}
|
|
@ -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 }
|
||||
|
||||
|
|
|
@ -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)),
|
||||
}
|
||||
|
|
|
@ -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(()),
|
||||
|
|
|
@ -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
113
crates/djls/src/lib.rs
Normal 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(())
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
# djls-agent
|
|
@ -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"]
|
|
@ -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"
|
|
@ -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())
|
|
@ -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
|
|
@ -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__)
|
||||
)
|
|
@ -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
|
|
@ -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
|
||||
|
|
@ -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)
|
|
@ -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: ...
|
|
@ -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)
|
|
@ -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: ...
|
|
@ -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)
|
|
@ -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: ...
|
|
@ -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)
|
|
@ -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: ...
|
|
@ -1 +0,0 @@
|
|||
../../LICENSE
|
|
@ -1 +0,0 @@
|
|||
# djls-server
|
|
@ -1 +0,0 @@
|
|||
../../crates
|
|
@ -1 +0,0 @@
|
|||
../../proto
|
|
@ -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"] },
|
||||
]
|
|
@ -1 +0,0 @@
|
|||
../../rust-toolchain.toml
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
syntax = "proto3";
|
||||
|
||||
package djls.v1.django;
|
||||
|
||||
message Project {
|
||||
string version = 3;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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
67
uv.lock
generated
|
@ -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"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue