Skip to content

Commit 74b6d4d

Browse files
committed
change initialization semantics (again)
1 parent d3c4fab commit 74b6d4d

File tree

6 files changed

+91
-68
lines changed

6 files changed

+91
-68
lines changed

lib/src/api.rs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,37 @@ impl OntoEnv {
222222
}
223223
}
224224

225+
/// Opens an existing environment rooted at `config.root`, or initializes a new one using
226+
/// the provided configuration when none exists yet.
227+
pub fn open_or_init(config: Config, read_only: bool) -> Result<Self> {
228+
if config.temporary {
229+
return Self::init(config, false);
230+
}
231+
232+
let root = config.root.clone();
233+
234+
if let Some(found_root) = find_ontoenv_root_from(&root) {
235+
let ontoenv_dir = found_root.join(".ontoenv");
236+
if ontoenv_dir.exists() {
237+
return Self::load_from_directory(found_root, read_only);
238+
}
239+
}
240+
241+
let ontoenv_dir = root.join(".ontoenv");
242+
if ontoenv_dir.exists() {
243+
return Self::load_from_directory(root, read_only);
244+
}
245+
246+
if read_only {
247+
return Err(anyhow::anyhow!(
248+
"OntoEnv directory not found at {} and read_only=true",
249+
ontoenv_dir.display()
250+
));
251+
}
252+
253+
Self::init(config, false)
254+
}
255+
225256
/// Creates a new online OntoEnv that searches for ontologies in the current directory.
226257
/// If an environment already exists, it will be loaded.
227258
/// The environment will be persisted to disk in the `.ontoenv` directory.
@@ -1642,3 +1673,36 @@ impl OntoEnv {
16421673
self.config.resolution_policy = policy;
16431674
}
16441675
}
1676+
1677+
#[cfg(test)]
1678+
mod tests {
1679+
use super::*;
1680+
use tempfile::tempdir;
1681+
1682+
#[test]
1683+
fn open_or_init_initializes_when_missing() {
1684+
let tmp = tempdir().unwrap();
1685+
let root = tmp.path().join("env");
1686+
std::fs::create_dir_all(&root).unwrap();
1687+
1688+
let config = Config::builder()
1689+
.root(root.clone())
1690+
.offline(true)
1691+
.temporary(false)
1692+
.no_search(true)
1693+
.build()
1694+
.unwrap();
1695+
1696+
{
1697+
let env = OntoEnv::open_or_init(config.clone(), false).unwrap();
1698+
assert!(root.join(".ontoenv").is_dir());
1699+
drop(env);
1700+
}
1701+
1702+
{
1703+
let env = OntoEnv::open_or_init(config, false).unwrap();
1704+
assert!(root.join(".ontoenv").is_dir());
1705+
drop(env);
1706+
}
1707+
}
1708+
}

lib/tests/test_ontoenv.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ macro_rules! setup {
4444
}
4545

4646
copy_file(&source_path, &dest_path).expect(format!("Failed to copy file from {} to {}", source_path.display(), dest_path.display()).as_str());
47-
47+
4848
// modify the 'last modified' time to the current time.
4949
// We must open with .write(true) to get permissions
5050
// to set metadata on Windows.
@@ -53,7 +53,7 @@ macro_rules! setup {
5353
.write(true) // Request write access
5454
.open(&dest_path)
5555
.expect(format!("Failed to open file {} with write perms", dest_path.display()).as_str());
56-
56+
5757
dest_file.set_modified(current_time)
5858
.expect(format!("Failed to set modified time for file {}", dest_path.display()).as_str());
5959
)*

python/example.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
from ontoenv import OntoEnv, version
22
from rdflib import Graph
3-
print(version)
43

54

5+
print(version)
6+
67
print("Make env")
78
env = OntoEnv(search_directories=["../brick"], strict=False, offline=False)
89
print(env)
@@ -25,7 +26,7 @@
2526
print(env2.store_path())
2627

2728
print("get brick again from URL")
28-
brick = env2.get("https://brickschema.org/schema/1.4/Brick")
29+
brick = env2.get_graph("https://brickschema.org/schema/1.4/Brick")
2930
print(len(brick))
3031
print(brick)
3132
print(type(brick))
@@ -35,10 +36,9 @@
3536
env2.import_graph(brick, "https://w3id.org/rec")
3637
brick.serialize("test.ttl", format="turtle")
3738

38-
print("qudtqk deps", env2.get_dependents('http://qudt.org/2.1/vocab/quantitykind'))
39+
print("qudtqk deps", env2.get_importers("http://qudt.org/2.1/vocab/quantitykind"))
3940

4041
# get an rdflib.Dataset (https://rdflib.readthedocs.io/en/stable/apidocs/rdflib.html#rdflib.Dataset)
4142
ds = env2.to_rdflib_dataset()
42-
for graphname in ds.graphs():
43-
graph = ds.graph(graphname)
44-
print(f"Graph {graphname} has {len(graph)} triples")
43+
for graph in list(ds.contexts()):
44+
print(f"Graph {graph.identifier} has {len(graph)} triples")

python/src/lib.rs

Lines changed: 4 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -9,28 +9,23 @@ use anyhow::Error;
99
#[cfg(feature = "cli")]
1010
use ontoenv_cli;
1111
use oxigraph::model::{BlankNode, Literal, NamedNode, NamedOrBlankNodeRef, Term};
12+
#[cfg(not(feature = "cli"))]
13+
use pyo3::exceptions::PyRuntimeError;
1214
use pyo3::{
1315
prelude::*,
1416
types::{IntoPyDict, PyString, PyTuple},
15-
exceptions::PyValueError,
1617
};
17-
#[cfg(not(feature = "cli"))]
18-
use pyo3::exceptions::PyRuntimeError;
1918
use std::borrow::Borrow;
2019
use std::collections::{HashMap, HashSet};
21-
use std::path::PathBuf;
2220
use std::ffi::OsStr;
21+
use std::path::PathBuf;
2322
use std::sync::{Arc, Mutex};
2423

2524
fn anyhow_to_pyerr(e: Error) -> PyErr {
2625
PyErr::new::<pyo3::exceptions::PyValueError, _>(e.to_string())
2726
}
2827

2928
// Helper function to format paths with forward slashes for cross-platform error messages
30-
fn format_path_for_error(path: &std::path::Path) -> String {
31-
path.to_string_lossy().replace('\\', "/")
32-
}
33-
3429
#[allow(dead_code)]
3530
struct MyTerm(Term);
3631
impl From<Result<Bound<'_, PyAny>, pyo3::PyErr>> for MyTerm {
@@ -232,15 +227,6 @@ impl OntoEnv {
232227
temporary: bool,
233228
no_search: bool,
234229
) -> PyResult<Self> {
235-
236-
// Check if OntoEnv() is called without any meaningful arguments
237-
// This implements the behavior expected by the tests
238-
if path.is_none() && root == "." && !recreate && !temporary {
239-
// Use forward slashes for cross-platform compatibility in error messages
240-
return Err(PyValueError::new_err(
241-
"OntoEnv directory not found at \"./.ontoenv\". You must provide a valid path or set recreate=True or temporary=True to create a new OntoEnv.",
242-
));
243-
}
244230
let mut root_path = path.clone().unwrap_or_else(|| PathBuf::from(root));
245231
// If the provided path points to a '.ontoenv' directory, treat its parent as the root
246232
if root_path
@@ -284,34 +270,11 @@ impl OntoEnv {
284270
.map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(e.to_string()))?;
285271

286272
let env = if cfg.temporary {
287-
// Explicit in-memory env
288273
OntoEnvRs::init(cfg, false).map_err(anyhow_to_pyerr)?
289274
} else if recreate {
290-
// Explicit create/overwrite at root_path
291275
OntoEnvRs::init(cfg, true).map_err(anyhow_to_pyerr)?
292276
} else {
293-
// Discover upward from root_path; load if found. If not found and not read-only,
294-
// initialize a new environment at the requested root.
295-
match ::ontoenv::api::find_ontoenv_root_from(&root_path) {
296-
Some(found_root) => OntoEnvRs::load_from_directory(found_root, read_only)
297-
.map_err(anyhow_to_pyerr)?,
298-
None => {
299-
// If a specific path was provided but no .ontoenv exists, raise error
300-
if path.is_some() {
301-
return Err(PyValueError::new_err(format!(
302-
"OntoEnv directory not found at: \"{}\"",
303-
format_path_for_error(&root_path.join(".ontoenv"))
304-
)));
305-
}
306-
if read_only {
307-
return Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(format!(
308-
"OntoEnv directory not found at: \"{}\" and read_only=True",
309-
format_path_for_error(&root_path.join(".ontoenv"))
310-
)));
311-
}
312-
OntoEnvRs::init(cfg, false).map_err(anyhow_to_pyerr)?
313-
}
314-
}
277+
OntoEnvRs::open_or_init(cfg, read_only).map_err(anyhow_to_pyerr)?
315278
};
316279

317280
let inner = Arc::new(Mutex::new(Some(env)));

python/tests/test_ontoenv_api.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,7 @@ def tearDown(self):
3737

3838
def test_constructor_default(self):
3939
"""Test default OntoEnv() constructor respects git-style discovery."""
40-
with self.assertRaises(ValueError):
41-
OntoEnv()
42-
self.env = OntoEnv(temporary=True)
40+
self.env = OntoEnv()
4341
self.assertIn("OntoEnv", repr(self.env))
4442

4543
def test_constructor_path(self):

python/tests/test_ontoenv_init.py

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
import shutil
33
import os
44
import tempfile
5-
import re
65
from pathlib import Path
76
from ontoenv import OntoEnv
87

@@ -68,25 +67,24 @@ def test_init_read_only(self):
6867
with self.assertRaisesRegex(ValueError, "Cannot add to read-only store"):
6968
env.add("file:///dummy.ttl")
7069

71-
def test_init_no_config_no_path_error(self):
72-
# Clean up potential leftover .ontoenv in cwd just in case
73-
if os.path.exists(".ontoenv"):
74-
if os.path.isfile(".ontoenv"):
75-
os.remove(".ontoenv")
76-
else:
77-
shutil.rmtree(".ontoenv")
78-
with self.assertRaisesRegex(ValueError, "OntoEnv directory not found at \"./.ontoenv\". You must provide a valid path or set recreate=True or temporary=True to create a new OntoEnv."):
79-
OntoEnv() # No args
70+
def test_init_no_config_creates_environment(self):
71+
original_cwd = Path.cwd()
72+
with tempfile.TemporaryDirectory() as td:
73+
os.chdir(td)
74+
try:
75+
env = OntoEnv() # No args; should auto-create
76+
self.assertTrue(Path(".ontoenv").is_dir())
77+
env.close()
78+
finally:
79+
os.chdir(original_cwd)
8080

81-
def test_init_path_no_env_error(self):
81+
def test_init_path_auto_initializes(self):
8282
with tempfile.TemporaryDirectory() as td:
8383
env_path = Path(td) / "no_env_here"
8484
env_path.mkdir()
85-
self.assertFalse((env_path / ".ontoenv").exists())
86-
# Be tolerant of macOS /private prefix differences by matching only the tail.
87-
tail_pattern = rf'OntoEnv directory not found at: "(.*/)?{re.escape(env_path.name)}/\.ontoenv"'
88-
with self.assertRaisesRegex(ValueError, tail_pattern):
89-
OntoEnv(path=env_path)
85+
env = OntoEnv(path=env_path)
86+
self.assertTrue((env_path / ".ontoenv").is_dir())
87+
env.close()
9088

9189
def test_init_temporary(self):
9290
with tempfile.TemporaryDirectory() as td:

0 commit comments

Comments
 (0)