Skip to content

Commit 0a6e975

Browse files
Get rid of all transport types and settle on Protobuf (#25)
* Get rid of all transport types and settle on Protobuf hope i don't regret this * Update Cargo.toml * Update agent.py
1 parent 643a479 commit 0a6e975

38 files changed

+1486
-687
lines changed

.editorconfig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ trim_trailing_whitespace = true
99
[{,.}{j,J}ustfile]
1010
indent_size = 4
1111

12-
[*.{py,rst,ini,md}]
12+
[*.{just,proto,py,rst,ini,md}]
1313
indent_size = 4
1414

1515
[*.py]

.just/proto.just

Lines changed: 72 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,84 @@ fmt:
2222
# Generate protobuf code for both Rust and Python
2323
[no-cd]
2424
gen:
25-
@just proto rust
26-
@just proto py
25+
@just proto rust
26+
@just proto py
2727

2828
# Generate protobuf code for Rust
2929
[no-cd]
3030
rust: check
31-
cargo build -p djls-types
31+
@just proto clean-rust
32+
cargo build -p djls-ipc
33+
34+
[private]
35+
clean-rust:
36+
#!/usr/bin/env python3
37+
from pathlib import Path
38+
import shutil
39+
40+
target_dir = Path("{{ justfile_directory() }}/target")
41+
42+
if target_dir.exists():
43+
for item in target_dir.rglob('*djls[_-]ipc*'):
44+
if item.is_file():
45+
item.unlink()
46+
elif item.is_dir():
47+
shutil.rmtree(item)
3248

3349
# Generate protobuf code for Python
3450
[no-cd]
3551
py: check
36-
protoc -I=proto --python_out=python/djls proto/*.proto
52+
@just proto clean-py
53+
protoc -I=proto \
54+
--python_out=python/djls/proto \
55+
--pyi_out=python/djls/proto \
56+
proto/v1/*.proto
57+
fd -t f "(_pb2\.py|_pb2\.pyi)$" python/djls/proto -x sed -i 's/from v1 import/from . import/g' {}
58+
fd -t d "." python/djls/proto -x touch {}//__init__.py
59+
@just proto py-add-warnings
60+
61+
[private]
62+
clean-py:
63+
#!/usr/bin/env python3
64+
from pathlib import Path
65+
import shutil
66+
67+
proto_dir = Path("{{ justfile_directory() }}/python/djls/proto")
68+
69+
for item in proto_dir.iterdir():
70+
if item.is_file():
71+
item.unlink()
72+
elif item.is_dir():
73+
shutil.rmtree(item)
74+
75+
[private]
76+
py-add-warnings:
77+
#!/usr/bin/env python3
78+
from pathlib import Path
79+
80+
def create_warning(proto_file: str) -> str:
81+
return f'''# WARNING: This file is generated by protobuf. DO NOT EDIT!
82+
# Any changes made to this file will be overwritten when the protobuf files are regenerated.
83+
# Source: {proto_file}
84+
85+
'''
86+
87+
proto_dir = Path("{{ justfile_directory() }}/python/djls/proto")
88+
proto_source_dir = Path("{{ justfile_directory() }}/proto")
89+
90+
proto_sources = {
91+
path.stem: path.relative_to(proto_source_dir)
92+
for path in proto_source_dir.glob('**/*.proto')
93+
}
94+
95+
for file_path in proto_dir.glob("**/*.py*"): # Catches both .py and .pyi in all subdirs
96+
proto_name = file_path.stem.removesuffix('_pb2')
97+
source_proto = proto_sources.get(proto_name)
3798

99+
content = file_path.read_text()
100+
if not content.startswith('# WARNING'):
101+
warning = create_warning(
102+
str(source_proto) if source_proto
103+
else "generated by py-init"
104+
)
105+
file_path.write_text(warning + content)

Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ djls-django = { path = "crates/djls-django" }
99
djls-ipc = { path = "crates/djls-ipc" }
1010
djls-python = { path = "crates/djls-python" }
1111
djls-server = { path = "crates/djls-server" }
12-
djls-types = { path = "crates/djls-types" }
1312
djls-worker = { path = "crates/djls-worker" }
1413

1514
anyhow = "1.0.94"

Justfile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ mod proto ".just/proto.just"
88
default:
99
@just --list
1010

11+
clean:
12+
rm -rf target/
13+
1114
# run pre-commit on all files
1215
lint:
1316
@just --fmt

crates/djlc-cli/src/main.rs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
use clap::{Args, Parser, Subcommand};
2-
use djls_ipc::{PythonProcess, Transport};
2+
use djls_ipc::v1::*;
3+
use djls_ipc::{ProcessError, PythonProcess, TransportError};
4+
use std::ffi::OsStr;
35
use std::time::Duration;
46

57
#[derive(Debug, Parser)]
@@ -41,8 +43,13 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
4143

4244
match cli.command {
4345
Commands::Serve(opts) => {
44-
let python =
45-
PythonProcess::new("djls.lsp", Transport::Json, opts.health_check_interval())?;
46+
println!("Starting LSP server...");
47+
let python = PythonProcess::new::<Vec<&OsStr>, &OsStr>(
48+
"djls.agent",
49+
None,
50+
opts.health_check_interval(),
51+
)?;
52+
println!("LSP server started, beginning to serve...");
4653
djls_server::serve(python).await?
4754
}
4855
}

crates/djls-django/src/apps.rs

Lines changed: 16 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
use djls_ipc::{JsonResponse, PythonProcess, TransportError, TransportMessage, TransportResponse};
2-
use serde::Deserialize;
1+
use djls_ipc::v1::*;
2+
use djls_ipc::{ProcessError, PythonProcess};
33
use std::fmt;
44

55
#[derive(Debug)]
@@ -20,23 +20,6 @@ impl fmt::Display for App {
2020
#[derive(Debug, Default)]
2121
pub struct Apps(Vec<App>);
2222

23-
#[derive(Debug, Deserialize)]
24-
struct InstalledAppsCheck {
25-
has_app: bool,
26-
}
27-
28-
impl TryFrom<JsonResponse> for InstalledAppsCheck {
29-
type Error = TransportError;
30-
31-
fn try_from(response: JsonResponse) -> Result<Self, Self::Error> {
32-
response
33-
.data()
34-
.clone()
35-
.ok_or_else(|| TransportError::Process("No data in response".to_string()))
36-
.and_then(|data| serde_json::from_value(data).map_err(TransportError::Json))
37-
}
38-
}
39-
4023
impl Apps {
4124
pub fn from_strings(apps: Vec<String>) -> Self {
4225
Self(apps.into_iter().map(App).collect())
@@ -54,18 +37,21 @@ impl Apps {
5437
self.apps().iter()
5538
}
5639

57-
pub fn check_installed(python: &mut PythonProcess, app: &str) -> Result<bool, TransportError> {
58-
let message = TransportMessage::Json("installed_apps_check".to_string());
59-
let response = python.send(message, Some(vec![app.to_string()]))?;
60-
match response {
61-
TransportResponse::Json(json_str) => {
62-
let json_response: JsonResponse = serde_json::from_str(&json_str)?;
63-
let result = InstalledAppsCheck::try_from(json_response)?;
64-
Ok(result.has_app)
65-
}
66-
_ => Err(TransportError::Process(
67-
"Unexpected response type".to_string(),
40+
pub fn check_installed(python: &mut PythonProcess, app: &str) -> Result<bool, ProcessError> {
41+
let request = messages::Request {
42+
command: Some(messages::request::Command::CheckAppInstalled(
43+
check::AppInstalledRequest {
44+
app_name: app.to_string(),
45+
},
6846
)),
47+
};
48+
49+
let response = python.send(request).map_err(ProcessError::Transport)?;
50+
51+
match response.result {
52+
Some(messages::response::Result::CheckAppInstalled(response)) => Ok(response.passed),
53+
Some(messages::response::Result::Error(e)) => Err(ProcessError::Health(e.message)),
54+
_ => Err(ProcessError::Response),
6955
}
7056
}
7157
}

crates/djls-django/src/django.rs

Lines changed: 35 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,26 @@
1-
use crate::apps::Apps;
21
use crate::gis::{check_gis_setup, GISError};
3-
use crate::templates::TemplateTags;
4-
use djls_ipc::{JsonResponse, PythonProcess, TransportError, TransportMessage, TransportResponse};
2+
use djls_ipc::v1::*;
3+
use djls_ipc::{ProcessError, PythonProcess, TransportError};
54
use djls_python::{ImportCheck, Python};
6-
use serde::Deserialize;
75
use std::fmt;
86

97
#[derive(Debug)]
108
pub struct DjangoProject {
119
py: Python,
1210
python: PythonProcess,
13-
settings_module: String,
14-
installed_apps: Apps,
15-
templatetags: TemplateTags,
16-
}
17-
18-
#[derive(Debug, Deserialize)]
19-
struct DjangoSetup {
20-
installed_apps: Vec<String>,
21-
templatetags: TemplateTags,
22-
}
23-
24-
impl DjangoSetup {
25-
pub fn setup(python: &mut PythonProcess) -> Result<JsonResponse, ProjectError> {
26-
let message = TransportMessage::Json("django_setup".to_string());
27-
let response = python.send(message, None)?;
28-
match response {
29-
TransportResponse::Json(json_str) => {
30-
let json_response: JsonResponse = serde_json::from_str(&json_str)?;
31-
Ok(json_response)
32-
}
33-
_ => Err(ProjectError::Transport(TransportError::Process(
34-
"Unexpected response type".to_string(),
35-
))),
36-
}
37-
}
11+
version: String,
3812
}
3913

4014
impl DjangoProject {
41-
fn new(
42-
py: Python,
43-
python: PythonProcess,
44-
settings_module: String,
45-
installed_apps: Apps,
46-
templatetags: TemplateTags,
47-
) -> Self {
15+
fn new(py: Python, python: PythonProcess, version: String) -> Self {
4816
Self {
4917
py,
5018
python,
51-
settings_module,
52-
installed_apps,
53-
templatetags,
19+
version,
5420
}
5521
}
5622

5723
pub fn setup(mut python: PythonProcess) -> Result<Self, ProjectError> {
58-
let settings_module =
59-
std::env::var("DJANGO_SETTINGS_MODULE").expect("DJANGO_SETTINGS_MODULE must be set");
60-
6124
let py = Python::setup(&mut python)?;
6225

6326
let has_django = ImportCheck::check(&mut python, Some(vec!["django".to_string()]))?;
@@ -74,45 +37,52 @@ impl DjangoProject {
7437
return Ok(Self {
7538
py,
7639
python,
77-
settings_module,
78-
installed_apps: Apps::default(),
79-
templatetags: TemplateTags::default(),
40+
version: String::new(),
8041
});
8142
}
8243

83-
let response = DjangoSetup::setup(&mut python)?;
84-
let setup: DjangoSetup = response
85-
.data()
86-
.clone()
87-
.ok_or_else(|| TransportError::Process("No data in response".to_string()))
88-
.and_then(|data| serde_json::from_value(data).map_err(TransportError::Json))?;
44+
let request = messages::Request {
45+
command: Some(messages::request::Command::DjangoGetProjectInfo(
46+
django::GetProjectInfoRequest {},
47+
)),
48+
};
8949

90-
Ok(Self::new(
50+
let response = python
51+
.send(request)
52+
.map_err(|e| ProjectError::Transport(e))?;
53+
54+
let version = match response.result {
55+
Some(messages::response::Result::DjangoGetProjectInfo(response)) => {
56+
response.project.unwrap().version
57+
}
58+
Some(messages::response::Result::Error(e)) => {
59+
return Err(ProjectError::Process(ProcessError::Health(e.message)));
60+
}
61+
_ => {
62+
return Err(ProjectError::Process(ProcessError::Response));
63+
}
64+
};
65+
66+
Ok(Self {
9167
py,
9268
python,
93-
settings_module,
94-
Apps::from_strings(setup.installed_apps.to_vec()),
95-
setup.templatetags,
96-
))
69+
version,
70+
})
9771
}
9872

9973
pub fn py(&self) -> &Python {
10074
&self.py
10175
}
10276

103-
fn settings_module(&self) -> &String {
104-
&self.settings_module
77+
fn version(&self) -> &String {
78+
&self.version
10579
}
10680
}
10781

10882
impl fmt::Display for DjangoProject {
10983
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
11084
writeln!(f, "Django Project")?;
111-
writeln!(f, "Settings Module: {}", self.settings_module)?;
112-
writeln!(f, "Installed Apps:")?;
113-
write!(f, "{}", self.installed_apps)?;
114-
writeln!(f, "Template Tags:")?;
115-
write!(f, "{}", self.templatetags)?;
85+
writeln!(f, "Version: {}", self.version)?;
11686
Ok(())
11787
}
11888
}
@@ -121,22 +91,18 @@ impl fmt::Display for DjangoProject {
12191
pub enum ProjectError {
12292
#[error("Django is not installed or cannot be imported")]
12393
DjangoNotFound,
124-
12594
#[error("IO error: {0}")]
12695
Io(#[from] std::io::Error),
127-
12896
#[error("GIS error: {0}")]
12997
Gis(#[from] GISError),
130-
13198
#[error("JSON parsing error: {0}")]
13299
Json(#[from] serde_json::Error),
133-
134100
#[error(transparent)]
135101
Packaging(#[from] djls_python::PackagingError),
136-
102+
#[error("Process error: {0}")]
103+
Process(#[from] ProcessError),
137104
#[error(transparent)]
138105
Python(#[from] djls_python::PythonError),
139-
140106
#[error("Transport error: {0}")]
141107
Transport(#[from] TransportError),
142108
}

crates/djls-django/src/gis.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::apps::Apps;
2-
use djls_ipc::{PythonProcess, TransportError};
2+
use djls_ipc::{ProcessError, PythonProcess, TransportError};
33
use std::process::Command;
44

55
pub fn check_gis_setup(python: &mut PythonProcess) -> Result<bool, GISError> {
@@ -17,10 +17,10 @@ pub fn check_gis_setup(python: &mut PythonProcess) -> Result<bool, GISError> {
1717
pub enum GISError {
1818
#[error("IO error: {0}")]
1919
Io(#[from] std::io::Error),
20-
2120
#[error("JSON parsing error: {0}")]
2221
Json(#[from] serde_json::Error),
23-
22+
#[error("Process error: {0}")]
23+
Process(#[from] ProcessError),
2424
#[error("Transport error: {0}")]
2525
Transport(#[from] TransportError),
2626
}

0 commit comments

Comments
 (0)