Skip to content

Commit

Permalink
adding recordlinker.utils.path for project_root and read_json
Browse files Browse the repository at this point in the history
  • Loading branch information
ericbuckley committed Nov 6, 2024
1 parent e3e8d6b commit 5fcfa5c
Show file tree
Hide file tree
Showing 8 changed files with 67 additions and 60 deletions.
2 changes: 1 addition & 1 deletion src/recordlinker/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import pydantic
import pydantic_settings

from recordlinker import utils
from recordlinker.utils import path as utils


class ConfigurationError(Exception):
Expand Down
4 changes: 2 additions & 2 deletions src/recordlinker/models/algorithm.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
from sqlalchemy import schema
from sqlalchemy import types as sqltypes

from recordlinker import utils
from recordlinker.config import ConfigurationError
from recordlinker.config import settings
from recordlinker.utils import functools as func_utils
from recordlinker.utils import path as path_utils

from .base import Base

Expand Down Expand Up @@ -143,7 +143,7 @@ def create_initial_algorithms(target, connection, **kw) -> typing.List[Algorithm
"""
if settings.initial_algorithms:
try:
data = utils.read_json(settings.initial_algorithms)
data = path_utils.read_json(settings.initial_algorithms)
except Exception as exc:
raise ConfigurationError("Error loading initial algorithms") from exc
if not any(algo.get("is_default") for algo in data):
Expand Down
25 changes: 0 additions & 25 deletions src/recordlinker/utils/__init__.py
Original file line number Diff line number Diff line change
@@ -1,25 +0,0 @@
import json
import pathlib


def project_root() -> pathlib.Path:
"""
Returns the path to the project root directory.
"""
root = pathlib.Path(__file__).resolve()
while root.name != "recordlinker":
if root.parent == root:
raise FileNotFoundError("recordlinker project root not found.")
root = root.parent
return root


def read_json(path: str) -> dict:
"""
Loads a JSON file.
"""
if not pathlib.Path(path).is_absolute():
# if path is relative, append to the project root
path = str(pathlib.Path(project_root(), path))
with open(path, "r") as fobj:
return json.load(fobj)
25 changes: 25 additions & 0 deletions src/recordlinker/utils/path.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import json
import pathlib


def project_root() -> pathlib.Path:
"""
Returns the path to the project root directory.
"""
root = pathlib.Path(__file__).resolve()
while root.name != "recordlinker":
if root.parent == root:
raise FileNotFoundError("recordlinker project root not found.")
root = root.parent
return root


def read_json(path: str) -> dict:
"""
Loads a JSON file.
"""
if not pathlib.Path(path).is_absolute():
# if path is relative, append to the project root
path = str(pathlib.Path(project_root(), path))
with open(path, "r") as fobj:
return json.load(fobj)
2 changes: 1 addition & 1 deletion tests/unit/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from recordlinker import database
from recordlinker import main
from recordlinker import models
from recordlinker import utils
from recordlinker.utils import path as utils


def load_test_json_asset(*paths: str) -> dict | list:
Expand Down
6 changes: 3 additions & 3 deletions tests/unit/models/test_algorithm.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ def test_no_default(self, monkeypatch, session):
Tests that the initial algorithms are created without a default algorithm
"""
monkeypatch.setattr(config.settings, "initial_algorithms", "file.json")
with unittest.mock.patch("recordlinker.utils.read_json") as read_json:
with unittest.mock.patch("recordlinker.utils.path.read_json") as read_json:
read_json.return_value = [{"is_default": False}]
with pytest.raises(config.ConfigurationError, match="No default algorithm found"):
models.create_initial_algorithms(None, session.connection())
Expand All @@ -159,7 +159,7 @@ def test_invalid_algorithm(self, monkeypatch, session):
Tests that an invalid algorithm raises a ValueError
"""
monkeypatch.setattr(config.settings, "initial_algorithms", "file.json")
with unittest.mock.patch("recordlinker.utils.read_json") as read_json:
with unittest.mock.patch("recordlinker.utils.path.read_json") as read_json:
read_json.return_value = [{"labell": "Algorithm 1", "is_default": True}]
with pytest.raises(config.ConfigurationError, match="Error creating initial algorithms"):
models.create_initial_algorithms(None, session.connection())
Expand All @@ -169,7 +169,7 @@ def test_create_initial_algorithms(self, monkeypatch, session):
Tests that the initial algorithms are created
"""
monkeypatch.setattr(config.settings, "initial_algorithms", "file.json")
with unittest.mock.patch("recordlinker.utils.read_json") as read_json:
with unittest.mock.patch("recordlinker.utils.path.read_json") as read_json:
read_json.return_value = [{"label": "Algorithm 1", "is_default": True}]
models.create_initial_algorithms(None, session.connection())
assert session.query(models.Algorithm).count() == 1
Expand Down
28 changes: 0 additions & 28 deletions tests/unit/utils/test_functools.py
Original file line number Diff line number Diff line change
@@ -1,39 +1,11 @@
import pathlib
import tempfile
import typing
import unittest.mock

import pytest

from recordlinker.linking import matchers
from recordlinker.utils import functools as utils


def test_project_root():
root = utils.project_root()
assert root.name == "recordlinker"

def test_project_root_not_found():
with unittest.mock.patch("pathlib.Path.resolve") as mock_resolve:
mock_resolve.return_value = pathlib.Path("/")
with pytest.raises(FileNotFoundError):
utils.project_root()

def test_read_json_relative():
tmp = utils.project_root() / "test.json"
with open(tmp, "w") as fobj:
fobj.write('{"key": "value"}')
assert utils.read_json("test.json") == {"key": "value"}
tmp.unlink()

def test_read_json_absolute():
tmp = tempfile.NamedTemporaryFile(suffix=".json")
with open(tmp.name, "w") as fobj:
fobj.write('{"key": "value"}')
assert utils.read_json(tmp.name) == {"key": "value"}
tmp.close()


def test_bind_functions():
funcs = {
"first_name": "func:recordlinker.linking.matchers.feature_match_fuzzy_string",
Expand Down
35 changes: 35 additions & 0 deletions tests/unit/utils/test_path.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import pathlib
import tempfile
import unittest.mock

import pytest

from recordlinker.utils import path as utils


def test_project_root():
root = utils.project_root()
assert root.name == "recordlinker"


def test_project_root_not_found():
with unittest.mock.patch("pathlib.Path.resolve") as mock_resolve:
mock_resolve.return_value = pathlib.Path("/")
with pytest.raises(FileNotFoundError):
utils.project_root()


def test_read_json_relative():
tmp = utils.project_root() / "test.json"
with open(tmp, "w") as fobj:
fobj.write('{"key": "value"}')
assert utils.read_json("test.json") == {"key": "value"}
tmp.unlink()


def test_read_json_absolute():
tmp = tempfile.NamedTemporaryFile(suffix=".json")
with open(tmp.name, "w") as fobj:
fobj.write('{"key": "value"}')
assert utils.read_json(tmp.name) == {"key": "value"}
tmp.close()

0 comments on commit 5fcfa5c

Please sign in to comment.