Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🥅 Check instance.modules when loading a lamindb schema module #946

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 57 additions & 8 deletions lamindb_setup/_check_setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@

import functools
import importlib as il
import inspect
import os
from typing import TYPE_CHECKING

from lamin_utils import logger

from ._silence_loggers import silence_loggers
from .core import django
from .core import django as django_lamin
from .core._settings import settings
from .core._settings_store import current_instance_settings_file
from .core.exceptions import DefaultMessageException
Expand All @@ -33,6 +34,10 @@
IS_LOADING: bool = False


class ModuleWasntConfigured(RuntimeError):
pass


# decorator to disable auto-connect when importing a module such as lamindb
def _loading(func: Callable):
@functools.wraps(func)
Expand Down Expand Up @@ -70,14 +75,56 @@
return None


# checks that the provided modules is in the modules of the provided instance
# or in the apps setup by django
def _check_module_in_instance_modules(
module: str, isettings: InstanceSettings | None = None
) -> None:
not_in_instance_msg = (
f"'{module}' is missing from this instance. "
"Please go to your instance settings page and add it under 'schema modules'."
)

if isettings is not None:
if module not in isettings.modules:
raise ModuleWasntConfigured(not_in_instance_msg)

Check warning on line 90 in lamindb_setup/_check_setup.py

View check run for this annotation

Codecov / codecov/patch

lamindb_setup/_check_setup.py#L90

Added line #L90 was not covered by tests
else:
return

from django.apps import apps

for app in apps.get_app_configs():
if module == app.name:
return
raise ModuleWasntConfigured(not_in_instance_msg)

Check warning on line 99 in lamindb_setup/_check_setup.py

View check run for this annotation

Codecov / codecov/patch

lamindb_setup/_check_setup.py#L99

Added line #L99 was not covered by tests


# infer the name of the module that calls this function
def _infer_callers_module_name() -> str | None:
stack = inspect.stack()
if len(stack) < 3:
return None

Check warning on line 106 in lamindb_setup/_check_setup.py

View check run for this annotation

Codecov / codecov/patch

lamindb_setup/_check_setup.py#L106

Added line #L106 was not covered by tests
module = inspect.getmodule(stack[2][0])
return module.__name__.partition(".")[0] if module is not None else None


# we make this a private function because in all the places it's used,
# users should not see it
def _check_instance_setup(from_module: str | None = None) -> bool:
if django.IS_SETUP:
if django_lamin.IS_SETUP:
# reload logic here because module might not yet have been imported
# upon first setup
if from_module is not None and from_module != "lamindb":
il.reload(il.import_module(from_module))
if from_module is not None:
if from_module != "lamindb":
_check_module_in_instance_modules(from_module)
il.reload(il.import_module(from_module))
else:
infer_module = _infer_callers_module_name()
if infer_module is not None and infer_module not in {
"lamindb",
"lamindb_setup",
}:
_check_module_in_instance_modules(infer_module)
return True
silence_loggers()
if os.environ.get("LAMINDB_MULTI_INSTANCE") == "true":
Expand All @@ -91,17 +138,19 @@
if (
from_module is not None
and settings.auto_connect
and not django.IS_SETUP
and not django_lamin.IS_SETUP
and not IS_LOADING
):
if not from_module == "lamindb":
if from_module != "lamindb":
_check_module_in_instance_modules(from_module, isettings)

import lamindb

il.reload(il.import_module(from_module))
else:
django.setup_django(isettings)
django_lamin.setup_django(isettings)
logger.important(f"connected lamindb: {isettings.slug}")
return django.IS_SETUP
return django_lamin.IS_SETUP
else:
if from_module is not None and settings.auto_connect:
logger.warning(InstanceNotSetupError.default_message)
Expand Down
3 changes: 2 additions & 1 deletion lamindb_setup/_connect_instance.py
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,8 @@
load_from_isettings(isettings, user=_user, write_settings=_write_settings)
if _reload_lamindb:
importlib.reload(importlib.import_module("lamindb"))
logger.important(f"connected lamindb: {isettings.slug}")
else:
logger.important(f"connected lamindb: {isettings.slug}")

Check warning on line 315 in lamindb_setup/_connect_instance.py

View check run for this annotation

Codecov / codecov/patch

lamindb_setup/_connect_instance.py#L315

Added line #L315 was not covered by tests
except Exception as e:
if isettings is not None:
if _write_settings:
Expand Down
20 changes: 8 additions & 12 deletions lamindb_setup/core/_settings_instance.py
Original file line number Diff line number Diff line change
Expand Up @@ -466,13 +466,11 @@ def _persist(self, write_to_disk: bool = True) -> None:
settings._instance_settings = self

def _init_db(self):
from lamindb_setup import _check_setup
from lamindb_setup._check_setup import _loading

from .django import setup_django

_check_setup.IS_LOADING = True
setup_django(self, init=True)
_check_setup.IS_LOADING = False
_loading(setup_django)(self, init=True)

from lamindb.models import Space

Expand All @@ -482,8 +480,6 @@ def _init_db(self):
)

def _load_db(self) -> tuple[bool, str]:
from lamindb_setup import _check_setup

# Is the database available and initialized as LaminDB?
# returns a tuple of status code and message
if self.dialect == "sqlite" and not self._sqlite_file.exists():
Expand All @@ -494,15 +490,15 @@ def _load_db(self) -> tuple[bool, str]:
f" {legacy_file} to {self._sqlite_file}"
)
return False, f"SQLite file {self._sqlite_file} does not exist"

from .django import setup_django

# we need the local sqlite to setup django
self._update_local_sqlite_file(lock_cloud_sqlite=self._is_cloud_sqlite)
# setting up django also performs a check for migrations & prints them
# as warnings
# this should fail, e.g., if the db is not reachable
_check_setup.IS_LOADING = True
setup_django(self)
_check_setup.IS_LOADING = False
from lamindb_setup._check_setup import _loading

from .django import setup_django

_loading(setup_django)(self)

return True, ""
Loading