diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f0675c2..9d2abf5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -35,6 +35,11 @@ jobs: enable-cache: true version: ${{ env.UV_VERSION }} + - name: Install Protoc + uses: arduino/setup-protoc@v3 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + - name: Install dependencies and build run: | uv sync --frozen diff --git a/.just/proto.just b/.just/proto.just new file mode 100644 index 0000000..9f40084 --- /dev/null +++ b/.just/proto.just @@ -0,0 +1,37 @@ +set unstable := true + +justfile := justfile_directory() + "/.just/proto.just" + +[private] +default: + @just --list --justfile {{ justfile }} + +[no-cd] +[private] +check: + #!/usr/bin/env sh + if ! command -v protoc > /dev/null 2>&1; then + echo "protoc is not installed. Please install protobuf-compiler" + exit 1 + fi + +[private] +fmt: + @just --fmt --justfile {{ justfile }} + +# Generate protobuf code for both Rust and Python +[no-cd] +gen: + @just proto rust + @just proto py + +# Generate protobuf code for Rust +[no-cd] +rust: check + cargo build -p djls-types + +# Generate protobuf code for Python +[no-cd] +py: check + protoc -I=proto --python_out=python/djls proto/*.proto + diff --git a/Cargo.toml b/Cargo.toml index f6f0309..63b90f7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,10 +9,13 @@ 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-types = { path = "crates/djls-types" } djls-worker = { path = "crates/djls-worker" } anyhow = "1.0.94" async-trait = "0.1.83" +prost = "0.13" +bytes = "1.9" serde = { version = "1.0.215", features = ["derive"] } serde_json = "1.0.133" thiserror = "2.0.6" diff --git a/Justfile b/Justfile index 04d8b25..29348a2 100644 --- a/Justfile +++ b/Justfile @@ -1,6 +1,8 @@ set dotenv-load := true set unstable := true +mod proto ".just/proto.just" + # List all available commands [private] default: diff --git a/crates/djlc-cli/Cargo.toml b/crates/djlc-cli/Cargo.toml index bc2d0bb..7349989 100644 --- a/crates/djlc-cli/Cargo.toml +++ b/crates/djlc-cli/Cargo.toml @@ -9,6 +9,7 @@ djls-ipc = { workspace = true } djls-server = { workspace = true } anyhow = { workspace = true } +serde_json = { workspace = true } tokio = { workspace = true } clap = { version = "4.5.23", features = ["derive"] } diff --git a/crates/djlc-cli/src/main.rs b/crates/djlc-cli/src/main.rs index c645b53..2338779 100644 --- a/crates/djlc-cli/src/main.rs +++ b/crates/djlc-cli/src/main.rs @@ -33,10 +33,6 @@ impl CommonOpts { enum Commands { /// Start the LSP server Serve(CommonOpts), - /// Get Python environment information - Info(CommonOpts), - /// Print the version - Version(CommonOpts), } #[tokio::main] @@ -49,22 +45,6 @@ async fn main() -> Result<(), Box> { PythonProcess::new("djls.lsp", Transport::Json, opts.health_check_interval())?; djls_server::serve(python).await? } - Commands::Info(opts) => { - let mut python = - PythonProcess::new("djls.lsp", Transport::Json, opts.health_check_interval())?; - match python.send("python_setup", None) { - Ok(info) => println!("{}", info), - Err(e) => eprintln!("Failed to get info: {}", e), - } - } - Commands::Version(opts) => { - let mut python = - PythonProcess::new("djls.lsp", Transport::Json, opts.health_check_interval())?; - match python.send("version", None) { - Ok(version) => println!("Python module version: {}", version), - Err(e) => eprintln!("Failed to get version: {}", e), - } - } } Ok(()) diff --git a/crates/djls-django/src/apps.rs b/crates/djls-django/src/apps.rs index 975dd82..11c2c68 100644 --- a/crates/djls-django/src/apps.rs +++ b/crates/djls-django/src/apps.rs @@ -1,4 +1,4 @@ -use djls_ipc::{parse_json_response, JsonResponse, PythonProcess, TransportError}; +use djls_ipc::{JsonResponse, PythonProcess, TransportError, TransportMessage, TransportResponse}; use serde::Deserialize; use std::fmt; @@ -55,10 +55,18 @@ impl Apps { } pub fn check_installed(python: &mut PythonProcess, app: &str) -> Result { - let response = python.send("installed_apps_check", Some(vec![app.to_string()]))?; - let response = parse_json_response(response)?; - let result = InstalledAppsCheck::try_from(response)?; - Ok(result.has_app) + let message = TransportMessage::Json("installed_apps_check".to_string()); + let response = python.send(message, Some(vec![app.to_string()]))?; + match response { + TransportResponse::Json(json_str) => { + let json_response: JsonResponse = serde_json::from_str(&json_str)?; + let result = InstalledAppsCheck::try_from(json_response)?; + Ok(result.has_app) + } + _ => Err(TransportError::Process( + "Unexpected response type".to_string(), + )), + } } } diff --git a/crates/djls-django/src/django.rs b/crates/djls-django/src/django.rs index 0ea1c41..d86be12 100644 --- a/crates/djls-django/src/django.rs +++ b/crates/djls-django/src/django.rs @@ -1,7 +1,7 @@ use crate::apps::Apps; use crate::gis::{check_gis_setup, GISError}; use crate::templates::TemplateTags; -use djls_ipc::{parse_json_response, JsonResponse, PythonProcess, TransportError}; +use djls_ipc::{JsonResponse, PythonProcess, TransportError, TransportMessage, TransportResponse}; use djls_python::{ImportCheck, Python}; use serde::Deserialize; use std::fmt; @@ -23,9 +23,17 @@ struct DjangoSetup { impl DjangoSetup { pub fn setup(python: &mut PythonProcess) -> Result { - let response = python.send("django_setup", None)?; - let response = parse_json_response(response)?; - Ok(response) + let message = TransportMessage::Json("django_setup".to_string()); + let response = python.send(message, None)?; + match response { + TransportResponse::Json(json_str) => { + let json_response: JsonResponse = serde_json::from_str(&json_str)?; + Ok(json_response) + } + _ => Err(ProjectError::Transport(TransportError::Process( + "Unexpected response type".to_string(), + ))), + } } } diff --git a/crates/djls-ipc/Cargo.toml b/crates/djls-ipc/Cargo.toml index 43c7fbd..a73957f 100644 --- a/crates/djls-ipc/Cargo.toml +++ b/crates/djls-ipc/Cargo.toml @@ -4,8 +4,12 @@ version = "0.0.0" edition = "2021" [dependencies] +djls-types = { workspace = true } + anyhow = { workspace = true } async-trait = { workspace = true } +prost = { workspace = true } +bytes = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } thiserror = { workspace = true } diff --git a/crates/djls-ipc/src/lib.rs b/crates/djls-ipc/src/lib.rs index 693af9b..21e0519 100644 --- a/crates/djls-ipc/src/lib.rs +++ b/crates/djls-ipc/src/lib.rs @@ -7,3 +7,5 @@ pub use transport::parse_raw_response; pub use transport::JsonResponse; pub use transport::Transport; pub use transport::TransportError; +pub use transport::TransportMessage; +pub use transport::TransportResponse; diff --git a/crates/djls-ipc/src/process.rs b/crates/djls-ipc/src/process.rs index fa3989b..922c745 100644 --- a/crates/djls-ipc/src/process.rs +++ b/crates/djls-ipc/src/process.rs @@ -1,4 +1,6 @@ -use crate::transport::{Transport, TransportError, TransportProtocol}; +use crate::transport::{ + Transport, TransportError, TransportMessage, TransportProtocol, TransportResponse, +}; use std::process::{Child, Command, Stdio}; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex}; @@ -72,9 +74,9 @@ impl PythonProcess { pub fn send( &mut self, - message: &str, + message: TransportMessage, args: Option>, - ) -> Result { + ) -> Result { let mut transport = self.transport.lock().unwrap(); transport.send(message, args) } diff --git a/crates/djls-ipc/src/transport.rs b/crates/djls-ipc/src/transport.rs index f2093d3..510f297 100644 --- a/crates/djls-ipc/src/transport.rs +++ b/crates/djls-ipc/src/transport.rs @@ -1,6 +1,9 @@ +use djls_types::proto::*; +use prost::Message; use serde::{Deserialize, Serialize}; use serde_json::Value; use std::fmt::Debug; +use std::io::Read; use std::io::{BufRead, BufReader, BufWriter, Write}; use std::process::{ChildStdin, ChildStdout}; use std::sync::{Arc, Mutex}; @@ -19,6 +22,7 @@ pub enum TransportError { pub enum Transport { Raw, Json, + Protobuf, } impl Transport { @@ -30,6 +34,7 @@ impl Transport { let transport_type = match self { Transport::Raw => "raw", Transport::Json => "json", + Transport::Protobuf => "protobuf", }; writeln!(stdin, "{}", transport_type).map_err(TransportError::Io)?; @@ -48,10 +53,25 @@ impl Transport { match self { Transport::Raw => Ok(Box::new(RawTransport::new(stdin, stdout)?)), Transport::Json => Ok(Box::new(JsonTransport::new(stdin, stdout)?)), + Transport::Protobuf => Ok(Box::new(ProtobufTransport::new(stdin, stdout)?)), } } } +#[derive(Debug)] +pub enum TransportMessage { + Raw(String), + Json(String), + Protobuf(ToAgent), +} + +#[derive(Debug)] +pub enum TransportResponse { + Raw(String), + Json(String), + Protobuf(FromAgent), +} + pub trait TransportProtocol: Debug + Send { fn new(stdin: ChildStdin, stdout: ChildStdout) -> Result where @@ -60,11 +80,15 @@ pub trait TransportProtocol: Debug + Send { fn clone_box(&self) -> Box; fn send_impl( &mut self, - message: &str, + message: TransportMessage, args: Option>, - ) -> Result; + ) -> Result; - fn send(&mut self, message: &str, args: Option>) -> Result { + fn send( + &mut self, + message: TransportMessage, + args: Option>, + ) -> Result { self.health_check()?; self.send_impl(message, args) } @@ -91,13 +115,16 @@ impl TransportProtocol for RawTransport { } fn health_check(&mut self) -> Result<(), TransportError> { - self.send_impl("health", None) - .and_then(|response| match response.as_str() { - "ok" => Ok(()), - other => Err(TransportError::Process(format!( + self.send_impl(TransportMessage::Raw("health".to_string()), None) + .and_then(|response| match response { + TransportResponse::Raw(s) if s == "ok" => Ok(()), + TransportResponse::Raw(other) => Err(TransportError::Process(format!( "Health check failed: {}", other ))), + _ => Err(TransportError::Process( + "Unexpected response type".to_string(), + )), }) } @@ -110,16 +137,24 @@ impl TransportProtocol for RawTransport { fn send_impl( &mut self, - message: &str, + message: TransportMessage, args: Option>, - ) -> Result { + ) -> Result { let mut writer = self.writer.lock().unwrap(); - if let Some(args) = args { - // Join command and args with spaces - writeln!(writer, "{} {}", message, args.join(" ")).map_err(TransportError::Io)?; - } else { - writeln!(writer, "{}", message).map_err(TransportError::Io)?; + match message { + TransportMessage::Raw(msg) => { + if let Some(args) = args { + writeln!(writer, "{} {}", msg, args.join(" ")).map_err(TransportError::Io)?; + } else { + writeln!(writer, "{}", msg).map_err(TransportError::Io)?; + } + } + _ => { + return Err(TransportError::Process( + "Raw transport only accepts raw messages".to_string(), + )) + } } writer.flush().map_err(TransportError::Io)?; @@ -127,7 +162,7 @@ impl TransportProtocol for RawTransport { let mut reader = self.reader.lock().unwrap(); let mut line = String::new(); reader.read_line(&mut line).map_err(TransportError::Io)?; - Ok(line.trim().to_string()) + Ok(TransportResponse::Raw(line.trim().to_string())) } } @@ -165,15 +200,21 @@ impl TransportProtocol for JsonTransport { } fn health_check(&mut self) -> Result<(), TransportError> { - self.send_impl("health", None).and_then(|response| { - let json: JsonResponse = serde_json::from_str(&response)?; - match json.status.as_str() { - "ok" => Ok(()), + self.send_impl(TransportMessage::Json("health".to_string()), None) + .and_then(|response| match response { + TransportResponse::Json(json) => { + let resp: JsonResponse = serde_json::from_str(&json)?; + match resp.status.as_str() { + "ok" => Ok(()), + _ => Err(TransportError::Process( + resp.error.unwrap_or_else(|| "Unknown error".to_string()), + )), + } + } _ => Err(TransportError::Process( - json.error.unwrap_or_else(|| "Unknown error".to_string()), + "Unexpected response type".to_string(), )), - } - }) + }) } fn clone_box(&self) -> Box { @@ -185,23 +226,110 @@ impl TransportProtocol for JsonTransport { fn send_impl( &mut self, - message: &str, + message: TransportMessage, args: Option>, - ) -> Result { - let command = JsonCommand { - command: message.to_string(), - args, - }; - + ) -> Result { let mut writer = self.writer.lock().unwrap(); - serde_json::to_writer(&mut *writer, &command)?; - writeln!(writer).map_err(TransportError::Io)?; + + match message { + TransportMessage::Json(msg) => { + let command = JsonCommand { command: msg, args }; + serde_json::to_writer(&mut *writer, &command)?; + writeln!(writer).map_err(TransportError::Io)?; + } + _ => { + return Err(TransportError::Process( + "JSON transport only accepts JSON messages".to_string(), + )) + } + } + writer.flush().map_err(TransportError::Io)?; let mut reader = self.reader.lock().unwrap(); let mut line = String::new(); reader.read_line(&mut line).map_err(TransportError::Io)?; - Ok(line.trim().to_string()) + Ok(TransportResponse::Json(line.trim().to_string())) + } +} + +#[derive(Debug)] +pub struct ProtobufTransport { + reader: Arc>>, + writer: Arc>>, +} + +impl TransportProtocol for ProtobufTransport { + fn new(stdin: ChildStdin, stdout: ChildStdout) -> Result { + Ok(Self { + reader: Arc::new(Mutex::new(BufReader::new(stdout))), + writer: Arc::new(Mutex::new(BufWriter::new(stdin))), + }) + } + + fn health_check(&mut self) -> Result<(), TransportError> { + let request = ToAgent { + command: Some(to_agent::Command::HealthCheck(HealthCheck {})), + }; + + match self.send_impl(TransportMessage::Protobuf(request), None)? { + TransportResponse::Protobuf(FromAgent { + message: Some(from_agent::Message::Error(e)), + }) => Err(TransportError::Process(e.message)), + TransportResponse::Protobuf(FromAgent { + message: Some(from_agent::Message::HealthCheck(_)), + }) => Ok(()), + _ => Err(TransportError::Process("Unexpected response".to_string())), + } + } + + fn clone_box(&self) -> Box { + Box::new(ProtobufTransport { + reader: self.reader.clone(), + writer: self.writer.clone(), + }) + } + + fn send_impl( + &mut self, + message: TransportMessage, + _args: Option>, + ) -> Result { + let mut writer = self.writer.lock().unwrap(); + + match message { + TransportMessage::Protobuf(msg) => { + let buf = msg.encode_to_vec(); + writer + .write_all(&(buf.len() as u32).to_be_bytes()) + .map_err(TransportError::Io)?; + writer.write_all(&buf).map_err(TransportError::Io)?; + } + _ => { + return Err(TransportError::Process( + "Protobuf transport only accepts protobuf messages".to_string(), + )) + } + } + + writer.flush().map_err(TransportError::Io)?; + + let mut reader = self.reader.lock().unwrap(); + 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)?; + + let response = FromAgent::decode(message_bytes.as_slice()) + .map_err(|e| TransportError::Process(e.to_string()))?; + + Ok(TransportResponse::Protobuf(response)) } } diff --git a/crates/djls-python/src/packaging.rs b/crates/djls-python/src/packaging.rs index 08a563c..b90b6ce 100644 --- a/crates/djls-python/src/packaging.rs +++ b/crates/djls-python/src/packaging.rs @@ -1,4 +1,4 @@ -use djls_ipc::{parse_json_response, JsonResponse, PythonProcess, TransportError}; +use djls_ipc::{JsonResponse, PythonProcess, TransportError, TransportMessage, TransportResponse}; use serde::Deserialize; use std::collections::HashMap; use std::fmt; @@ -78,10 +78,18 @@ impl ImportCheck { python: &mut PythonProcess, modules: Option>, ) -> Result { - let response = python.send("has_import", modules)?; - let response = parse_json_response(response)?; - let check = Self::try_from(response)?; - Ok(check.can_import) + let message = TransportMessage::Json("has_import".to_string()); + let response = python.send(message, modules)?; + match response { + TransportResponse::Json(json_str) => { + let json_response: JsonResponse = serde_json::from_str(&json_str)?; + let check = Self::try_from(json_response)?; + Ok(check.can_import) + } + _ => Err(PackagingError::Transport(TransportError::Process( + "Unexpected response type".to_string(), + ))), + } } } diff --git a/crates/djls-python/src/python.rs b/crates/djls-python/src/python.rs index 052bb10..5c43bee 100644 --- a/crates/djls-python/src/python.rs +++ b/crates/djls-python/src/python.rs @@ -1,5 +1,5 @@ use crate::packaging::{Packages, PackagingError}; -use djls_ipc::{parse_json_response, JsonResponse, PythonProcess, TransportError}; +use djls_ipc::{JsonResponse, PythonProcess, TransportError, TransportMessage, TransportResponse}; use serde::Deserialize; use std::fmt; use std::path::PathBuf; @@ -72,9 +72,17 @@ impl TryFrom for Python { impl Python { pub fn setup(python: &mut PythonProcess) -> Result { - let response = python.send("python_setup", None)?; - let response = parse_json_response(response)?; - Ok(Self::try_from(response)?) + let message = TransportMessage::Json("python_setup".to_string()); + let response = python.send(message, None)?; + match response { + TransportResponse::Json(json_str) => { + let json_response: JsonResponse = serde_json::from_str(&json_str)?; + Ok(Self::try_from(json_response)?) + } + _ => Err(PythonError::Transport(TransportError::Process( + "Unexpected response type".to_string(), + ))), + } } } diff --git a/crates/djls-types/Cargo.toml b/crates/djls-types/Cargo.toml new file mode 100644 index 0000000..0025c42 --- /dev/null +++ b/crates/djls-types/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "djls-types" +version = "0.0.0" +edition = "2021" + +[dependencies] +prost = { workspace = true } +bytes = { workspace = true } + +[build-dependencies] +prost-build = "0.13" diff --git a/crates/djls-types/build.rs b/crates/djls-types/build.rs new file mode 100644 index 0000000..596e628 --- /dev/null +++ b/crates/djls-types/build.rs @@ -0,0 +1,24 @@ +use std::fs; +use std::path::PathBuf; + +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 protos: Vec<_> = fs::read_dir(&proto_dir) + .unwrap() + .filter_map(Result::ok) + .filter(|entry| entry.path().extension().and_then(|s| s.to_str()) == Some("proto")) + .map(|entry| entry.path()) + .collect(); + + prost_build::compile_protos( + &protos + .iter() + .map(|p| p.to_str().unwrap()) + .collect::>(), + &[proto_dir], + ) + .unwrap(); +} diff --git a/crates/djls-types/src/lib.rs b/crates/djls-types/src/lib.rs new file mode 100644 index 0000000..65f6aa0 --- /dev/null +++ b/crates/djls-types/src/lib.rs @@ -0,0 +1,5 @@ +pub mod proto { + include!(concat!(env!("OUT_DIR"), "/djls.rs")); +} + +use proto::*; diff --git a/proto/messages.proto b/proto/messages.proto new file mode 100644 index 0000000..ba0dc34 --- /dev/null +++ b/proto/messages.proto @@ -0,0 +1,29 @@ +syntax = "proto3"; + +package djls; + +// Commands we send to Python +message ToAgent { + oneof command { + HealthCheck health_check = 1; + Shutdown shutdown = 2; + } +} + +message HealthCheck {} +message Shutdown {} + +// Responses we get back +message FromAgent { + oneof message { + HealthCheckResponse health_check = 1; + Error error = 2; + } +} + +message HealthCheckResponse {} + +message Error { + string message = 1; + string traceback = 2; // Optional stack trace from Python +} diff --git a/pyproject.toml b/pyproject.toml index b5f0e85..fb5c0b6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,6 +13,7 @@ authors = [ requires-python = ">=3.9" dependencies = [ "django>=4.2", + "protobuf>=5.29.1", ] [tool.hatch.build.targets.wheel] diff --git a/python/djls/messages_pb2.py b/python/djls/messages_pb2.py new file mode 100644 index 0000000..4a516b8 --- /dev/null +++ b/python/djls/messages_pb2.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: 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, + '', + 'messages.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0emessages.proto\x12\x04\x64jls\"c\n\x07ToAgent\x12)\n\x0chealth_check\x18\x01 \x01(\x0b\x32\x11.djls.HealthCheckH\x00\x12\"\n\x08shutdown\x18\x02 \x01(\x0b\x32\x0e.djls.ShutdownH\x00\x42\t\n\x07\x63ommand\"\r\n\x0bHealthCheck\"\n\n\x08Shutdown\"g\n\tFromAgent\x12\x31\n\x0chealth_check\x18\x01 \x01(\x0b\x32\x19.djls.HealthCheckResponseH\x00\x12\x1c\n\x05\x65rror\x18\x02 \x01(\x0b\x32\x0b.djls.ErrorH\x00\x42\t\n\x07message\"\x15\n\x13HealthCheckResponse\"+\n\x05\x45rror\x12\x0f\n\x07message\x18\x01 \x01(\t\x12\x11\n\ttraceback\x18\x02 \x01(\tb\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'messages_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None + _globals['_TOAGENT']._serialized_start=24 + _globals['_TOAGENT']._serialized_end=123 + _globals['_HEALTHCHECK']._serialized_start=125 + _globals['_HEALTHCHECK']._serialized_end=138 + _globals['_SHUTDOWN']._serialized_start=140 + _globals['_SHUTDOWN']._serialized_end=150 + _globals['_FROMAGENT']._serialized_start=152 + _globals['_FROMAGENT']._serialized_end=255 + _globals['_HEALTHCHECKRESPONSE']._serialized_start=257 + _globals['_HEALTHCHECKRESPONSE']._serialized_end=278 + _globals['_ERROR']._serialized_start=280 + _globals['_ERROR']._serialized_end=323 +# @@protoc_insertion_point(module_scope) diff --git a/uv.lock b/uv.lock index 37644d3..dd30ad6 100644 --- a/uv.lock +++ b/uv.lock @@ -63,6 +63,7 @@ version = "0.1.0" source = { editable = "." } dependencies = [ { name = "django" }, + { name = "protobuf" }, ] [package.dev-dependencies] @@ -72,7 +73,10 @@ dev = [ ] [package.metadata] -requires-dist = [{ name = "django", specifier = ">=4.2" }] +requires-dist = [ + { name = "django", specifier = ">=4.2" }, + { name = "protobuf", specifier = ">=5.29.1" }, +] [package.metadata.requires-dev] dev = [ @@ -80,6 +84,22 @@ dev = [ { name = "ruff", specifier = ">=0.8.2" }, ] +[[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 = "ruff" version = "0.8.2"