diff --git a/src/finam/interfaces.py b/src/finam/interfaces.py index 2379977d75be575d1a63222c4c6a67a68954b6f2..d720281bd94207ee9731501961479b846b6ef96c 100644 --- a/src/finam/interfaces.py +++ b/src/finam/interfaces.py @@ -138,6 +138,11 @@ class IComponent(ABC): def status(self): """The component's current status.""" + @property + @abstractmethod + def metadata(self): + """The component's meta data.""" + class ITimeComponent(IComponent, ABC): """Interface for components with a time step.""" @@ -453,6 +458,11 @@ class IOutput(ABC): class IAdapter(IInput, IOutput, ABC): """Interface for adapters.""" + @property + @abstractmethod + def metadata(self): + """The adapter's meta data.""" + class NoBranchAdapter: """Interface to mark adapters as allowing only a single end point.""" diff --git a/src/finam/schedule.py b/src/finam/schedule.py index e85a40a3700d706b6cd372a84f689a5cf8059875..ecc2d36abed82e96aeb9101d37d4f0c5f35c05d7 100644 --- a/src/finam/schedule.py +++ b/src/finam/schedule.py @@ -428,6 +428,34 @@ class Composition(Loggable): f"Expecting one of [{', '.join(map(str, desired_list))}]" ) + @property + def metadata(self): + """ + Meta data for all components and adapters. + Can only be used after ``connect``. + + Raises + ------ + FinamStatusError + Raises the error if ``connect`` was not called. + """ + if not self.is_connected: + with ErrorLogger(self.logger): + raise FinamStatusError( + "can't get meta data for a composition before connect was called" + ) + + md = {} + for mod in self.modules: + key = f"{mod.name}@{id(mod)}" + md[key] = mod.metadata + + for ada in self.adapters: + key = f"{ada.name}@{id(ada)}" + md[key] = ada.metadata + + return md + def _collect_adapters_input(inp: IInput, out_adapters: set): src = inp.get_source() diff --git a/src/finam/sdk/adapter.py b/src/finam/sdk/adapter.py index 1f5c3d3982b84da572f885eef47b71c014555b1d..bdc8a7288e8d904272d363500f015f959cb03bcb 100644 --- a/src/finam/sdk/adapter.py +++ b/src/finam/sdk/adapter.py @@ -67,6 +67,11 @@ class Adapter(IAdapter, Input, Output, ABC): """bool: if the adapter needs push.""" return False + @property + def metadata(self): + """The adapter's meta data.""" + return {} + @final def push_data(self, data, time): """Push data into the output. diff --git a/src/finam/sdk/component.py b/src/finam/sdk/component.py index baf3610d9c7e7434ad88764332bb19244349d4f5..ef38a0dc9b86f2839d3f6aaf30f45bd9d8d190d0 100644 --- a/src/finam/sdk/component.py +++ b/src/finam/sdk/component.py @@ -218,6 +218,11 @@ class Component(IComponent, Loggable, ABC): """Component name.""" return self._name + @property + def metadata(self): + """The component's meta data.""" + return {} + @property def logger_name(self): """Logger name derived from base logger name and class name.""" diff --git a/tests/core/test_schedule.py b/tests/core/test_schedule.py index e0a7e6b6195a3daaa85f6017a4ddf8d299b325fd..d9f36822b342442b2059beb7b80eef63654f4a3b 100644 --- a/tests/core/test_schedule.py +++ b/tests/core/test_schedule.py @@ -965,6 +965,31 @@ class TestComposition(unittest.TestCase): self.assertEqual([1, 8, 13], updates["A"]) self.assertEqual([1, 2, 5, 8, 11], updates["B"]) + def test_metadata(self): + module1 = MockupComponent( + callbacks={"Output": lambda t: t.day}, step=timedelta(1.0) + ) + module2 = MockupDependentComponent(step=timedelta(1.0)) + + composition = Composition([module2, module1]) + composition.initialize() + + ada1 = fm.adapters.Scale(1.0) + ada2 = fm.adapters.Scale(1.0) + module1.outputs["Output"] >> ada1 >> ada2 >> module2.inputs["Input"] + + with self.assertRaises(FinamStatusError) as context: + _ = composition.metadata + + composition.connect() + + md = composition.metadata + + self.assertIn(f"{module1.name}@{id(module1)}", md) + self.assertIn(f"{module2.name}@{id(module2)}", md) + self.assertIn(f"{ada1.name}@{id(ada1)}", md) + self.assertIn(f"{ada2.name}@{id(ada2)}", md) + if __name__ == "__main__": unittest.main()