Examples
The BalderHub data package provides various types of objects for defining data class models. These are particularly helpful for data-related tests, such as those in balderhub-crud or balderhub-auth.
Defining Your Own Data Model Structure
This package allows you to define your own data model structure by using the
balderhub.data.lib.utils.SingleDataItem as the base class:
from typing import Optional
from balderhub.data.lib.utils import SingleDataItem
class AuthorDataItem(SingleDataItem):
id: int
first_name: str
last_name: str
def get_unique_identification(self):
return self.id
class BookCategoryDataItem(SingleDataItem):
id: int
name: str
def get_unique_identification(self):
return self.id
class BookDataItem(SingleDataItem):
id: int
title: str
author: AuthorDataItem
category: Optional[BookCategoryDataItem] = None
def get_unique_identification(self):
return self.id
You can use the following types within balderhub.data.lib.utils.SingleDataItem:
base types like str, int, bool, float, etc.
declare a type as optional with typing.Optional[int] or typing.Union[int, None] (both are equal and accepted)
nested data items (referred data item needs to be another
balderhub.data.lib.utils.SingleDataItem)
What is not allowed:
multiple possible types defined by typing.Union[int, float, ..] (except the Optional like definition: typing.Union[int, None])
list types other than list[MyDataItem] (where MyDataItem is a SingleDataItem subclass)
dictionary types: use nested data items for that
Interacting with SingleDataItem Objects
The package provides various ways to interact with data items.
For more details, see the API documentation for balderhub.data.lib.utils.SingleDataItem and
balderhub.data.lib.utils.SingleDataItemCollection.
For referencing fields, especially nested fields, use balderhub.data.lib.utils.LookupFieldString.
For example, to access the author’s last name from a BookDataItem instance, use author__last_name. The
project will automatically convert strings like that to LookupFieldString objects.
Using the Data Environment Feature
Most features use the balderhub.data.lib.scenario_features.DataEnvironmentFeature, which
allows you to define your own data samples.
from balderhub.data.lib.scenario_features.data_environment_feature import DataEnvironmentFeature
from tests.lib.utils import data_items
from tests.lib.setup_features.dut_simulator_feature import DutSimulatorFeature
from tests.lib.utils.dut_simulator import DutSimulator
class TestDataEnvironment(DataEnvironmentFeature):
sim = DutSimulatorFeature()
def load_data(self):
self._add_data(data_items.AuthorDataItem(id=1, first_name='J.K.', last_name='Rowling'))
self._add_data(data_items.AuthorDataItem(id=2, first_name='J.R.R.', last_name='Tolkien'))
self._add_data(data_items.AuthorDataItem(id=3, first_name='A.B.C.', last_name='Alphabet'))
self._add_data(data_items.BookCategoryDataItem(id=1, name='Fantasy'))
self._add_data(data_items.BookCategoryDataItem(id=2, name='Science Fiction'))
self._add_data(data_items.BookCategoryDataItem(id=3, name='Action'))
self._add_data(data_items.BookDataItem(
id=1, title='Harry Potter and the Sorcerer’s Stone',
author=self.get(data_items.AuthorDataItem, 1),
category=self.get(data_items.BookCategoryDataItem, 1))
)
self._add_data(data_items.BookDataItem(
id=2, title='Harry Potter and the Order of the Phoenix',
author=self.get(data_items.AuthorDataItem, 1),
category=self.get(data_items.BookCategoryDataItem, 1))
)
self._add_data(data_items.BookDataItem(
id=3, title='The Hobbit',
author=self.get(data_items.AuthorDataItem, 2),
category=self.get(data_items.BookCategoryDataItem, 1))
)
def sync_environment(self):
"""Triggers data synchronization when the remote data needs to be synced"""
self.sim.dut_simulator.remove_all()
for cur_author in self.get_all_for(data_items.AuthorDataItem):
self.sim.dut_simulator._all_authors[cur_author.id] = DutSimulator.Author(
id=cur_author.id, first_name=cur_author.first_name, last_name=cur_author.last_name
)
for cur_category in self.get_all_for(data_items.BookCategoryDataItem):
self.sim.dut_simulator._all_categories[cur_category.id] = DutSimulator.Category(
id=cur_category.id, name=cur_category.name
)
for cur_book in self.get_all_for(data_items.BookDataItem):
self.sim.dut_simulator._all_books[cur_book.id] = DutSimulator.Book(
id=cur_book.id, title=cur_book.title, author__id=cur_book.author.id, category__id=cur_book.category.id
)
You need to define two methods, the method balderhub.data.lib.scenario_features.DataEnvironmentFeature.load_data()
which describes the full data sample and the method
balderhub.data.lib.scenario_features.DataEnvironmentFeature.sync_environment(), that will be executed when the data
samples need to be synced. This will be done by most projects automatically. You only need to make sure that you
have an implementation of this method.
If you would like to run tests under different data samples, you can define multiple versions of your DataEnvironmentFeature with different data sets and assign them to different setups:
class SetupBase(balder.Setup):
class DUT(balder.Device):
data = FictitiousDataEnvironmentFeature()
...
...
class SetupWithLiveData(balder.Setup):
class DUT(balder.Device):
data = LiveDataEnvironmentFeature()
...
...
Initial Data Config Features
This project provides two main features used in other BalderHub projects, like balderhub-crud or balderhub-auth.
These features are the balderhub.data.lib.scenario_features.InitialDataConfig and the
balderhub.data.lib.scenario_features.AccessibleInitialDataConfig.
The InitialDataConfig will be assigned to the same device, that has the DataEnvironmentFeature and describes all initial loaded data for a specific data item. On the other side, the AccessibleInitialDataConfig is a feature, that is assigned to the remote device that is used to access the data from the DUT. It can return the same data as the remote InitialDataConfig feature or return a subset of it. This is mainly used for data, that has special object permission and can only be accessed by a specific user group.
When using the balderhub.data.lib.scenario_features.DataEnvironmentFeature, you will not need to provide an
implementation for both of them, just use the setup factories:
class SetupBase(balder.Setup):
class DUT(balder.Device):
data = FictitiousDataEnvironmentFeature()
initial_data = balderhub.data.lib.setup_features.factories.AutoInitialDataConfigFactory.get_for(AuthorDataItem)()
...
@balder.connect(DUT, over_connection=balder.Connection)
class User(balder.Device):
accessible_data = balderhub.data.lib.setup_features.factories.AutoAccessibleInitialDataConfigFactory.get_for(AuthorDataItem)(Master='DUT')
Working with SingleDataItemCollection
SingleDataItemCollection is a collection of SingleDataItem instances with additional utility methods like filtering, getting by attributes, sorting, and comparison.
Example:
from balderhub.data.lib.utils import SingleDataItemCollection
# Assuming AuthorDataItem defined earlier
authors = SingleDataItemCollection([
AuthorDataItem(id=1, first_name='J.K.', last_name='Rowling'),
AuthorDataItem(id=2, first_name='J.R.R.', last_name='Tolkien'),
])
# Get by identifier
rowling = authors.get_by(id=1)
# Filter (requires a Filter subclass, see below)
fantasy_authors = authors.filter(FantasyAuthorFilter())
# Compare collections
expected = SingleDataItemCollection([rowling])
if expected.compare(fantasy_authors):
print(\"Collections match\")
Using Filters
Define custom filters for SingleDataItemCollection.filter():
from balderhub.data.lib.utils import Filter
class FantasyAuthorFilter(Filter):
def apply(self, item: AuthorDataItem) -> bool:
return item.last_name == 'Rowling' # example logic
filtered = authors.filter(FantasyAuthorFilter())