Skip to content

Commit 62396b4

Browse files
Remove vestigal concrete Project database, keeping trait
1 parent 318a395 commit 62396b4

File tree

9 files changed

+136
-66
lines changed

9 files changed

+136
-66
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/djls-project/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ extension-module = []
88
default = []
99

1010
[dependencies]
11+
djls-workspace = { workspace = true }
12+
1113
pyo3 = { workspace = true }
1214
salsa = { workspace = true }
1315
which = { workspace = true}

crates/djls-project/src/db.rs

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,31 @@
1+
//! Project-specific database trait and queries.
2+
//!
3+
//! This module extends the workspace database trait with project-specific
4+
//! functionality including metadata access and Python environment discovery.
5+
6+
use djls_workspace::Db as WorkspaceDb;
7+
18
use crate::meta::ProjectMetadata;
9+
use crate::python::PythonEnvironment;
210

11+
/// Project-specific database trait extending the workspace database
312
#[salsa::db]
4-
pub trait Db: salsa::Database {
13+
pub trait Db: WorkspaceDb {
14+
/// Get the project metadata containing root path and venv configuration
515
fn metadata(&self) -> &ProjectMetadata;
616
}
717

8-
#[salsa::db]
9-
#[derive(Clone)]
10-
pub struct ProjectDatabase {
11-
storage: salsa::Storage<ProjectDatabase>,
12-
metadata: ProjectMetadata,
13-
}
14-
15-
impl ProjectDatabase {
16-
pub fn new(metadata: ProjectMetadata) -> Self {
17-
let storage = salsa::Storage::new(None);
18+
/// Find the Python environment for the project.
19+
///
20+
/// This Salsa tracked function discovers the Python environment based on:
21+
/// 1. Explicit venv path from metadata
22+
/// 2. VIRTUAL_ENV environment variable
23+
/// 3. Common venv directories in project root (.venv, venv, env, .env)
24+
/// 4. System Python as fallback
25+
#[salsa::tracked]
26+
pub fn find_python_environment(db: &dyn Db) -> Option<PythonEnvironment> {
27+
let project_path = db.metadata().root().as_path();
28+
let venv_path = db.metadata().venv().and_then(|p| p.to_str());
1829

19-
Self { storage, metadata }
20-
}
30+
PythonEnvironment::new(project_path, venv_path)
2131
}
22-
23-
#[salsa::db]
24-
impl Db for ProjectDatabase {
25-
fn metadata(&self) -> &ProjectMetadata {
26-
&self.metadata
27-
}
28-
}
29-
30-
#[salsa::db]
31-
impl salsa::Database for ProjectDatabase {}

crates/djls-project/src/lib.rs

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ use std::fmt;
88
use std::path::Path;
99
use std::path::PathBuf;
1010

11-
use db::ProjectDatabase;
12-
use meta::ProjectMetadata;
11+
pub use db::find_python_environment;
12+
pub use db::Db;
13+
pub use meta::ProjectMetadata;
1314
use pyo3::prelude::*;
14-
use python::find_python_environment;
15-
use python::PythonEnvironment;
15+
pub use python::PythonEnvironment;
1616
pub use templatetags::TemplateTags;
1717

1818
#[derive(Debug)]
@@ -32,11 +32,9 @@ impl DjangoProject {
3232
}
3333
}
3434

35-
pub fn initialize(&mut self, venv_path: Option<&str>) -> PyResult<()> {
36-
let venv_pathbuf = venv_path.map(PathBuf::from);
37-
let metadata = ProjectMetadata::new(self.path.clone(), venv_pathbuf);
38-
let db = ProjectDatabase::new(metadata);
39-
self.env = find_python_environment(&db);
35+
pub fn initialize(&mut self, db: &dyn Db) -> PyResult<()> {
36+
// Use the database to find the Python environment
37+
self.env = find_python_environment(db);
4038
if self.env.is_none() {
4139
return Err(PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(
4240
"Could not find Python environment",

crates/djls-project/src/python.rs

Lines changed: 52 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,8 @@ use std::path::PathBuf;
44

55
use pyo3::prelude::*;
66

7-
use crate::db::Db;
87
use crate::system;
98

10-
#[salsa::tracked]
11-
pub fn find_python_environment(db: &dyn Db) -> Option<PythonEnvironment> {
12-
let project_path = db.metadata().root().as_path();
13-
let venv_path = db.metadata().venv().and_then(|p| p.to_str());
14-
15-
PythonEnvironment::new(project_path, venv_path)
16-
}
17-
189
#[derive(Clone, Debug, PartialEq)]
1910
pub struct PythonEnvironment {
2011
python_path: PathBuf,
@@ -23,7 +14,7 @@ pub struct PythonEnvironment {
2314
}
2415

2516
impl PythonEnvironment {
26-
fn new(project_path: &Path, venv_path: Option<&str>) -> Option<Self> {
17+
pub fn new(project_path: &Path, venv_path: Option<&str>) -> Option<Self> {
2718
if let Some(path) = venv_path {
2819
let prefix = PathBuf::from(path);
2920
if let Some(env) = Self::from_venv_prefix(&prefix) {
@@ -703,12 +694,57 @@ mod tests {
703694
}
704695
}
705696

706-
// Add tests for the salsa tracked function
707697
mod salsa_integration {
698+
use std::sync::Arc;
699+
700+
use djls_workspace::FileSystem;
701+
use djls_workspace::InMemoryFileSystem;
702+
708703
use super::*;
709-
use crate::db::ProjectDatabase;
704+
use crate::db::find_python_environment;
705+
use crate::db::Db as ProjectDb;
710706
use crate::meta::ProjectMetadata;
711707

708+
/// Test implementation of ProjectDb for unit tests
709+
#[salsa::db]
710+
#[derive(Clone)]
711+
struct TestDatabase {
712+
storage: salsa::Storage<TestDatabase>,
713+
metadata: ProjectMetadata,
714+
fs: Arc<dyn FileSystem>,
715+
}
716+
717+
impl TestDatabase {
718+
fn new(metadata: ProjectMetadata) -> Self {
719+
Self {
720+
storage: salsa::Storage::new(None),
721+
metadata,
722+
fs: Arc::new(InMemoryFileSystem::new()),
723+
}
724+
}
725+
}
726+
727+
#[salsa::db]
728+
impl salsa::Database for TestDatabase {}
729+
730+
#[salsa::db]
731+
impl djls_workspace::Db for TestDatabase {
732+
fn fs(&self) -> Arc<dyn FileSystem> {
733+
self.fs.clone()
734+
}
735+
736+
fn read_file_content(&self, path: &std::path::Path) -> std::io::Result<String> {
737+
self.fs.read_to_string(path)
738+
}
739+
}
740+
741+
#[salsa::db]
742+
impl ProjectDb for TestDatabase {
743+
fn metadata(&self) -> &ProjectMetadata {
744+
&self.metadata
745+
}
746+
}
747+
712748
#[test]
713749
fn test_find_python_environment_with_salsa_db() {
714750
let project_dir = tempdir().unwrap();
@@ -721,8 +757,8 @@ mod tests {
721757
let metadata =
722758
ProjectMetadata::new(project_dir.path().to_path_buf(), Some(venv_prefix.clone()));
723759

724-
// Create a ProjectDatabase with the metadata
725-
let db = ProjectDatabase::new(metadata);
760+
// Create a TestDatabase with the metadata
761+
let db = TestDatabase::new(metadata);
726762

727763
// Call the tracked function
728764
let env = find_python_environment(&db);
@@ -756,8 +792,8 @@ mod tests {
756792
// Create a metadata instance with project path but no explicit venv path
757793
let metadata = ProjectMetadata::new(project_dir.path().to_path_buf(), None);
758794

759-
// Create a ProjectDatabase with the metadata
760-
let db = ProjectDatabase::new(metadata);
795+
// Create a TestDatabase with the metadata
796+
let db = TestDatabase::new(metadata);
761797

762798
// Mock to ensure VIRTUAL_ENV is not set
763799
let _guard = system::mock::MockGuard;

crates/djls-server/src/db.rs

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
//! Concrete Salsa database implementation for the Django Language Server.
22
//!
33
//! This module provides the concrete [`DjangoDatabase`] that implements all
4-
//! the database traits from workspace and template crates. This follows Ruff's
5-
//! architecture pattern where the concrete database lives at the top level.
4+
//! the database traits from workspace, template, and project crates. This follows
5+
//! Ruff's architecture pattern where the concrete database lives at the top level.
66
77
use std::path::Path;
88
use std::path::PathBuf;
@@ -11,6 +11,8 @@ use std::sync::Arc;
1111
use std::sync::Mutex;
1212

1313
use dashmap::DashMap;
14+
use djls_project::Db as ProjectDb;
15+
use djls_project::ProjectMetadata;
1416
use djls_templates::db::Db as TemplateDb;
1517
use djls_workspace::db::Db as WorkspaceDb;
1618
use djls_workspace::db::SourceFile;
@@ -23,6 +25,7 @@ use salsa::Setter;
2325
/// This database implements all the traits from various crates:
2426
/// - [`WorkspaceDb`] for file system access and core operations
2527
/// - [`TemplateDb`] for template parsing and diagnostics
28+
/// - [`ProjectDb`] for project metadata and Python environment
2629
#[salsa::db]
2730
#[derive(Clone)]
2831
pub struct DjangoDatabase {
@@ -32,6 +35,9 @@ pub struct DjangoDatabase {
3235
/// Maps paths to [`SourceFile`] entities for O(1) lookup.
3336
files: Arc<DashMap<PathBuf, SourceFile>>,
3437

38+
/// Project metadata containing root path and venv configuration.
39+
metadata: ProjectMetadata,
40+
3541
storage: salsa::Storage<Self>,
3642

3743
// The logs are only used for testing and demonstrating reuse:
@@ -49,6 +55,7 @@ impl Default for DjangoDatabase {
4955
Self {
5056
fs: Arc::new(InMemoryFileSystem::new()),
5157
files: Arc::new(DashMap::new()),
58+
metadata: ProjectMetadata::new(PathBuf::from("/test"), None),
5259
storage: salsa::Storage::new(Some(Box::new({
5360
let logs = logs.clone();
5461
move |event| {
@@ -68,11 +75,16 @@ impl Default for DjangoDatabase {
6875
}
6976

7077
impl DjangoDatabase {
71-
/// Create a new [`DjangoDatabase`] with the given file system and file map.
72-
pub fn new(file_system: Arc<dyn FileSystem>, files: Arc<DashMap<PathBuf, SourceFile>>) -> Self {
78+
/// Create a new [`DjangoDatabase`] with the given file system, file map, and project metadata.
79+
pub fn new(
80+
file_system: Arc<dyn FileSystem>,
81+
files: Arc<DashMap<PathBuf, SourceFile>>,
82+
metadata: ProjectMetadata,
83+
) -> Self {
7384
Self {
7485
fs: file_system,
7586
files,
87+
metadata,
7688
storage: salsa::Storage::new(None),
7789
#[cfg(test)]
7890
logs: Arc::new(Mutex::new(None)),
@@ -149,3 +161,10 @@ impl WorkspaceDb for DjangoDatabase {
149161

150162
#[salsa::db]
151163
impl TemplateDb for DjangoDatabase {}
164+
165+
#[salsa::db]
166+
impl ProjectDb for DjangoDatabase {
167+
fn metadata(&self) -> &ProjectMetadata {
168+
&self.metadata
169+
}
170+
}

crates/djls-server/src/server.rs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -149,12 +149,7 @@ impl LanguageServer for DjangoLanguageServer {
149149

150150
let init_result = {
151151
let mut session_lock = session_arc.lock().await;
152-
if let Some(project) = session_lock.project_mut().as_mut() {
153-
project.initialize(venv_path.as_deref())
154-
} else {
155-
// Project was removed between read and write locks
156-
Ok(())
157-
}
152+
session_lock.initialize_project()
158153
};
159154

160155
match init_result {

crates/djls-server/src/session.rs

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ use std::sync::Arc;
99
use dashmap::DashMap;
1010
use djls_conf::Settings;
1111
use djls_project::DjangoProject;
12+
use djls_project::ProjectMetadata;
13+
use pyo3::PyResult;
1214
use djls_workspace::db::SourceFile;
1315
use djls_workspace::paths;
1416
use djls_workspace::PositionEncoding;
@@ -63,23 +65,29 @@ impl Session {
6365
std::env::current_dir().ok()
6466
});
6567

66-
let (project, settings) = if let Some(path) = &project_path {
68+
let (project, settings, metadata) = if let Some(path) = &project_path {
6769
let settings =
6870
djls_conf::Settings::new(path).unwrap_or_else(|_| djls_conf::Settings::default());
6971

7072
let project = Some(djls_project::DjangoProject::new(path.clone()));
73+
74+
// Create metadata for the project with venv path from settings
75+
let venv_path = settings.venv_path().map(PathBuf::from);
76+
let metadata = ProjectMetadata::new(path.clone(), venv_path);
7177

72-
(project, settings)
78+
(project, settings, metadata)
7379
} else {
74-
(None, Settings::default())
80+
// Default metadata for when there's no project path
81+
let metadata = ProjectMetadata::new(PathBuf::from("."), None);
82+
(None, Settings::default(), metadata)
7583
};
7684

7785
// Create workspace for buffer management
7886
let workspace = Workspace::new();
7987

80-
// Create the concrete database with the workspace's file system
88+
// Create the concrete database with the workspace's file system and metadata
8189
let files = Arc::new(DashMap::new());
82-
let db = DjangoDatabase::new(workspace.file_system(), files);
90+
let db = DjangoDatabase::new(workspace.file_system(), files, metadata);
8391

8492
Self {
8593
db,
@@ -130,6 +138,20 @@ impl Session {
130138
f(&mut self.db)
131139
}
132140

141+
/// Get a reference to the database for project operations.
142+
pub fn database(&self) -> &DjangoDatabase {
143+
&self.db
144+
}
145+
146+
/// Initialize the project with the database.
147+
pub fn initialize_project(&mut self) -> PyResult<()> {
148+
if let Some(project) = self.project.as_mut() {
149+
project.initialize(&self.db)
150+
} else {
151+
Ok(())
152+
}
153+
}
154+
133155
/// Open a document in the session.
134156
///
135157
/// Updates both the workspace buffers and database. Creates the file in

crates/djls-workspace/Cargo.toml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@ version = "0.0.0"
44
edition = "2021"
55

66
[dependencies]
7-
djls-project = { workspace = true }
8-
97
anyhow = { workspace = true }
108
camino = { workspace = true }
119
dashmap = { workspace = true }

0 commit comments

Comments
 (0)