Skip to content

Commit 5974c51

Browse files
Remove vestigal concrete Project database, keeping trait (#198)
1 parent 318a395 commit 5974c51

File tree

10 files changed

+140
-66
lines changed

10 files changed

+140
-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/meta.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,17 @@ pub struct ProjectMetadata {
77
}
88

99
impl ProjectMetadata {
10+
#[must_use]
1011
pub fn new(root: PathBuf, venv: Option<PathBuf>) -> Self {
1112
ProjectMetadata { root, venv }
1213
}
1314

15+
#[must_use]
1416
pub fn root(&self) -> &PathBuf {
1517
&self.root
1618
}
1719

20+
#[must_use]
1821
pub fn venv(&self) -> Option<&PathBuf> {
1922
self.venv.as_ref()
2023
}

crates/djls-project/src/python.rs

Lines changed: 53 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,8 @@ pub struct PythonEnvironment {
2314
}
2415

2516
impl PythonEnvironment {
26-
fn new(project_path: &Path, venv_path: Option<&str>) -> Option<Self> {
17+
#[must_use]
18+
pub fn new(project_path: &Path, venv_path: Option<&str>) -> Option<Self> {
2719
if let Some(path) = venv_path {
2820
let prefix = PathBuf::from(path);
2921
if let Some(env) = Self::from_venv_prefix(&prefix) {
@@ -703,12 +695,57 @@ mod tests {
703695
}
704696
}
705697

706-
// Add tests for the salsa tracked function
707698
mod salsa_integration {
699+
use std::sync::Arc;
700+
701+
use djls_workspace::FileSystem;
702+
use djls_workspace::InMemoryFileSystem;
703+
708704
use super::*;
709-
use crate::db::ProjectDatabase;
705+
use crate::db::find_python_environment;
706+
use crate::db::Db as ProjectDb;
710707
use crate::meta::ProjectMetadata;
711708

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

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

727764
// Call the tracked function
728765
let env = find_python_environment(&db);
@@ -756,8 +793,8 @@ mod tests {
756793
// Create a metadata instance with project path but no explicit venv path
757794
let metadata = ProjectMetadata::new(project_dir.path().to_path_buf(), None);
758795

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

762799
// Mock to ensure VIRTUAL_ENV is not set
763800
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,11 +9,13 @@ use std::sync::Arc;
99
use dashmap::DashMap;
1010
use djls_conf::Settings;
1111
use djls_project::DjangoProject;
12+
use djls_project::ProjectMetadata;
1213
use djls_workspace::db::SourceFile;
1314
use djls_workspace::paths;
1415
use djls_workspace::PositionEncoding;
1516
use djls_workspace::TextDocument;
1617
use djls_workspace::Workspace;
18+
use pyo3::PyResult;
1719
use tower_lsp_server::lsp_types;
1820
use url::Url;
1921

@@ -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()));
7173

72-
(project, settings)
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);
77+
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

0 commit comments

Comments
 (0)