Skip to content

Commit

Permalink
Fix singleton
Browse files Browse the repository at this point in the history
  • Loading branch information
Kamil Rybacki committed Jul 16, 2024
1 parent 9105b15 commit f974ab6
Show file tree
Hide file tree
Showing 5 changed files with 68 additions and 43 deletions.
77 changes: 44 additions & 33 deletions phaistos/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@


class Manager:
discover: bool
logger: typing.ClassVar[logging.Logger] = MANAGER_LOGGER

_discover: bool
_current_schemas_path: typing.ClassVar[str] = ''
_schemas: dict[str, SchemaInstancesFactory] = {}

_started: typing.ClassVar[bool] = False
__instance: typing.ClassVar[Manager | None] = None

__instance: typing.Optional[Manager] = None

def validate(self, data: dict, schema: str) -> ValidationResults:
self.logger.info(f'Validating data against schema: {schema}')
Expand All @@ -33,32 +33,40 @@ def validate(self, data: dict, schema: str) -> ValidationResults:
def start(
cls,
discover: bool = True,
schemas_path: str | None = None
schemas_path: str = ''
) -> Manager:
cls._current_schemas_path = schemas_path or os.environ['PHAISTOS__SCHEMA_PATH'] # type: ignore
cls._discover = discover
if 'PHAISTOS__DISABLE_SCHEMA_DISCOVERY' in os.environ:
cls._discover = False
if not cls._started:
cls.logger.info('Starting Phaistos manager!')
cls._started = True
if 'PHAISTOS__DISABLE_SCHEMA_DISCOVERY' in os.environ:
discover = False
cls.__instance = cls(discover)
cls._current_schemas_path = schemas_path or os.environ.get('PHAISTOS__SCHEMA_PATH', '')
cls.__instance = cls()
return cls.__instance # type: ignore

@classmethod
def _purge(cls) -> None:
cls.__instance = None
def __new__(
cls,
*args,
**kwargs
) -> Manager:
if cls.__instance:
return cls.__instance
cls.logger.info('Starting Phaistos manager!')
if not cls._current_schemas_path and cls._discover:
raise RuntimeError(
'Schemas path must be provided or PHAISTOS__SCHEMA_PATH environment variable must be set'
)
cls._started = True
if cls._discover:
cls.get_available_schemas()
return super().__new__(cls)

@classmethod
def reset(cls) -> None:
cls._started = False
cls._current_schemas_path = ''
cls._schemas = {}

def __init__(self, discover: bool) -> None:
if not self._started:
raise RuntimeError(
'Validator must be started using Manager.start()'
)
if discover:
self._schemas = self.get_available_schemas()

def get_factory(self, name: str) -> SchemaInstancesFactory:
"""
Get a schema factory by name
Expand All @@ -75,49 +83,52 @@ def get_factory(self, name: str) -> SchemaInstancesFactory:
)
return self._schemas[name]

def get_available_schemas(self) -> dict[str, SchemaInstancesFactory]:
discovered_schemas = getattr(self, '_schemas', {})
@classmethod
def get_available_schemas(cls) -> dict[str, SchemaInstancesFactory]:
discovered_schemas = getattr(cls, '_schemas', {})
try:
for schema in self.__discover_schemas(self._current_schemas_path):
for schema in cls.__discover_schemas(cls._current_schemas_path):
discovered_schemas[schema.transpilation_name] = SchemaInstancesFactory(
name=schema.transpilation_name,
_model=schema
)
except tuple(DISCOVERY_EXCEPTIONS.keys()) as schema_discovery_error:
self.logger.error(
cls.logger.error(
DISCOVERY_EXCEPTIONS.get(type(schema_discovery_error), f'Error while discovering schemas: {schema_discovery_error}')
)
raise schema_discovery_error

self.logger.info(
cls.logger.info(
f'Available schemas: {", ".join(discovered_schemas.keys())}'
)
return discovered_schemas

def __discover_schemas(self, target_path: str) -> list[type[TranspiledSchema]]:
self.logger.info(f'Discovering schemas in: {target_path}')
@classmethod
def __discover_schemas(cls, target_path: str) -> list[type[TranspiledSchema]]:
cls.logger.info(f'Discovering schemas in: {target_path}')
schemas: list[type[TranspiledSchema]] = []
for schema in os.listdir(target_path):
if schema.startswith('_'):
continue

schema_path = f'{target_path}/{schema}'
if not os.path.isdir(schema_path):
self.logger.info(f'Importing schema: {schema_path}')
cls.logger.info(f'Importing schema: {schema_path}')

with open(schema_path, 'r', encoding='utf-8') as schema_file:
schema_data = yaml.safe_load(schema_file)
self.load_schema(schema_data)
cls.load_schema(schema_data)
continue

nested_schemas = self.__discover_schemas(schema_path)
nested_schemas = cls.__discover_schemas(schema_path)
schemas.extend(nested_schemas)
return schemas

def load_schema(self, schema: SchemaInputFile) -> str:
self.logger.info(f'Loading schema: {schema["name"]}')
@classmethod
def load_schema(cls, schema: SchemaInputFile) -> str:
cls.logger.info(f'Loading schema: {schema["name"]}')
schema_class = Transpiler.make_schema(schema)
self._schemas[schema['name']] = SchemaInstancesFactory(
cls._schemas[schema['name']] = SchemaInstancesFactory(
name=schema_class.transpilation_name,
_model=schema_class
)
Expand Down
2 changes: 1 addition & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
# pylint: disable=protected-access
@pytest.fixture(autouse=True)
def reset_manager() -> None:
phaistos.Manager._purge()
phaistos.Manager.reset()


def create_mock_schema_data(applied_properties: dict[str, phaistos.typings.RawSchemaProperty]) -> dict[str, typing.Any]:
Expand Down
6 changes: 3 additions & 3 deletions tests/consts.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ class MockRawSchemaProperty(phaistos.typings.RawSchemaProperty):
] = [
(
"""
self._current_schemas_path = '/invalid/path'
cls._current_schemas_path = '/invalid/path'
""",
FileNotFoundError,
),
Expand All @@ -165,7 +165,7 @@ class MockRawSchemaProperty(phaistos.typings.RawSchemaProperty):
except FileNotFoundError:
pass
open(test_file_path, 'w').close()
self._current_schemas_path = test_file_path
cls._current_schemas_path = test_file_path
""",
NotADirectoryError,
),
Expand All @@ -177,7 +177,7 @@ class MockRawSchemaProperty(phaistos.typings.RawSchemaProperty):
except FileExistsError:
pass
os.chmod(test_dir_path, 0o04111)
self._current_schemas_path = test_dir_path
cls._current_schemas_path = test_dir_path
""",
PermissionError,
),
Expand Down
14 changes: 14 additions & 0 deletions tests/test_manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from phaistos import Manager


def test_manager_as_singleton():
assert id(
Manager.start(discover=False) # First instantiation
) == id(
Manager.start(discover=False) # Second instantiation - should return the same instance
)
assert id(
Manager()
) == id(
Manager()
)
12 changes: 6 additions & 6 deletions tests/test_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,10 @@ def test_schema_discovery_exceptions(
os.environ['PHAISTOS__SCHEMA_PATH'] = consts.TESTS_ASSETS_PATH

original_get_available_schemas = copy.deepcopy(
phaistos.Manager.get_available_schemas # pylint: disable=protected-access
phaistos.Manager.get_available_schemas
)

def patched_get_available_schemas(self: phaistos.Manager):
def patched_get_available_schemas():
patch_function = types.FunctionType(
compile(
textwrap.dedent(hot_patch),
Expand All @@ -59,11 +59,11 @@ def patched_get_available_schemas(self: phaistos.Manager):
),
globals={
**globals(),
'self': self
'cls': phaistos.Manager
}
)
patch_function() # pylint: disable=not-callable
return original_get_available_schemas(self)
return original_get_available_schemas()

monkeypatch.setattr(
phaistos.Manager,
Expand All @@ -80,7 +80,7 @@ def patched_get_available_schemas(self: phaistos.Manager):
def test_schema_discovery_disabled(logger) -> None:
logger.info('Testing schema discovery disabled')
manager = phaistos.Manager.start(discover=False)
assert manager._schemas == {} # pylint: disable=protected-access
assert not manager._schemas # pylint: disable=protected-access


@pytest.mark.order(3)
Expand All @@ -91,7 +91,7 @@ def test_manual_path_specification(logger) -> None:
schemas_path=consts.TESTS_ASSETS_PATH
)
assert manager._current_schemas_path == consts.TESTS_ASSETS_PATH # pylint: disable=protected-access
assert manager._schemas != {} # pylint: disable=protected-access
assert not manager._schemas # pylint: disable=protected-access
os.environ['PHAISTOS__SCHEMA_PATH'] = original_path


Expand Down

0 comments on commit f974ab6

Please sign in to comment.