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
This commit is contained in:
Josh Thomas 2024-12-12 16:53:49 -06:00 committed by GitHub
parent 643a47953e
commit 0a6e975ca5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
38 changed files with 1484 additions and 685 deletions

View file

@ -1,4 +1,5 @@
use djls_ipc::{JsonResponse, PythonProcess, TransportError, TransportMessage, TransportResponse};
use djls_ipc::v1::*;
use djls_ipc::{ProcessError, PythonProcess, TransportError};
use serde::Deserialize;
use std::collections::HashMap;
use std::fmt;
@ -6,15 +7,25 @@ use std::path::PathBuf;
#[derive(Clone, Debug, Deserialize)]
pub struct Package {
name: String,
version: String,
location: Option<PathBuf>,
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.name, self.version)?;
if let Some(location) = &self.location {
write!(f, "{} {}", self.dist_name, self.dist_version)?;
if let Some(location) = &self.dist_location {
write!(f, " ({})", location.display())?;
}
Ok(())
@ -30,6 +41,12 @@ impl Packages {
}
}
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))
@ -39,7 +56,7 @@ impl FromIterator<(String, Package)> for Packages {
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.name.cmp(&b.name));
packages.sort_by(|a, b| a.dist_name.cmp(&b.dist_name));
if packages.is_empty() {
writeln!(f, " (no packages installed)")?;
@ -57,18 +74,6 @@ pub struct ImportCheck {
can_import: bool,
}
impl TryFrom<JsonResponse> for ImportCheck {
type Error = TransportError;
fn try_from(response: JsonResponse) -> Result<Self, Self::Error> {
response
.data()
.clone()
.ok_or_else(|| TransportError::Process("No data in response".to_string()))
.and_then(|data| serde_json::from_value(data).map_err(TransportError::Json))
}
}
impl ImportCheck {
pub fn can_import(&self) -> bool {
self.can_import
@ -76,19 +81,24 @@ impl ImportCheck {
pub fn check(
python: &mut PythonProcess,
modules: Option<Vec<String>>,
_modules: Option<Vec<String>>,
) -> Result<bool, PackagingError> {
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)
let request = messages::Request {
command: Some(messages::request::Command::CheckDjangoAvailable(
check::DjangoAvailableRequest {},
)),
};
let response = python
.send(request)
.map_err(|e| PackagingError::Transport(e))?;
match response.result {
Some(messages::response::Result::CheckDjangoAvailable(response)) => Ok(response.passed),
Some(messages::response::Result::Error(e)) => {
Err(PackagingError::Process(ProcessError::Health(e.message)))
}
_ => Err(PackagingError::Transport(TransportError::Process(
"Unexpected response type".to_string(),
))),
_ => Err(PackagingError::Process(ProcessError::Response)),
}
}
}
@ -103,6 +113,8 @@ pub enum PackagingError {
#[error("Transport error: {0}")]
Transport(#[from] TransportError),
#[error("Process error: {0}")]
Process(#[from] ProcessError),
#[error("UTF-8 conversion error: {0}")]
Utf8(#[from] std::string::FromUtf8Error),

View file

@ -1,5 +1,6 @@
use crate::packaging::{Packages, PackagingError};
use djls_ipc::{JsonResponse, PythonProcess, TransportError, TransportMessage, TransportResponse};
use djls_ipc::v1::*;
use djls_ipc::{ProcessError, PythonProcess, TransportError};
use serde::Deserialize;
use std::fmt;
use std::path::PathBuf;
@ -8,16 +9,45 @@ use std::path::PathBuf;
pub struct VersionInfo {
major: u8,
minor: u8,
patch: u8,
suffix: Option<String>,
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.patch)?;
if let Some(suffix) = &self.suffix {
write!(f, "{}", suffix)?;
}
write!(f, "{}.{}.{}", self.major, self.minor, self.micro)?;
Ok(())
}
}
@ -58,30 +88,52 @@ pub struct Python {
packages: Packages,
}
impl TryFrom<JsonResponse> for Python {
type Error = TransportError;
impl Python {
pub fn setup(python: &mut PythonProcess) -> Result<Self, PythonError> {
let request = messages::Request {
command: Some(messages::request::Command::PythonGetEnvironment(
python::GetEnvironmentRequest {},
)),
};
fn try_from(response: JsonResponse) -> Result<Self, Self::Error> {
response
.data()
.clone()
.ok_or_else(|| TransportError::Process("No data in response".to_string()))
.and_then(|data| serde_json::from_value(data).map_err(TransportError::Json))
let response = python.send(request).map_err(PythonError::Transport)?;
match response.result {
Some(messages::response::Result::PythonGetEnvironment(response)) => response
.python
.ok_or_else(|| PythonError::Process(ProcessError::Response))
.map(Into::into),
Some(messages::response::Result::Error(e)) => {
Err(PythonError::Process(ProcessError::Health(e.message)))
}
_ => Err(PythonError::Process(ProcessError::Response)),
}
}
}
impl Python {
pub fn setup(python: &mut PythonProcess) -> Result<Self, PythonError> {
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(),
))),
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(),
}
}
}
@ -107,25 +159,20 @@ impl fmt::Display for Python {
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),
}