class Address(ff.ValueObject): street_address: str = ff.required(str) locality: str = ff.required(str) region: str = ff.required(str) postal_code: str = ff.required(str) country: str = ff.required(str) formatted: str = ff.optional(str)
class Client(ff.AggregateRoot): id: str = ff.id_() name: str = ff.required(str) grant_type: str = ff.required(str, validators=[ ff.IsOneOf( (authorization_code, implicit, resource_owner_password_credentials, client_credentials)) ]) response_type: str = ff.optional( str, validators=[ff.IsOneOf(response_type_choices)]) scopes: str = ff.required(str) default_redirect_uri: str = ff.required(str) redirect_uris: List[str] = ff.list_() allowed_response_types: List[str] = ff.list_( validators=[ff.IsOneOf(('code', 'token'))]) def validate_redirect_uri(self, redirect_uri: str): return redirect_uri in self.redirect_uris def validate_response_type(self, response_type: str): return response_type in self.allowed_response_types def validate_scopes(self, scopes: List[str]): for scope in scopes: if scope not in self.scopes: return False return True
class Table: entity: sql.Entity = ff.required() relationships: List[sql.Relationship] = ff.required() name: str = None columns: List[Column] = ff.list_() constraints: List = ff.list_() _pks: List[str] = ff.list_() def __post_init__(self): self._initialize() def _initialize(self): self.name = inflection.tableize(self.entity.entity.__name__) for field_ in self.entity.fields: st = field_.sqlalchemy_type if st is not None: self._build_regular_column(field_, st) elif self._needs_relation_column(field_): self._build_relation_column(field_) self._build_pk() def _build_pk(self): self.constraints.append( PrimaryKeyConstraint(*self._pks, name=f'{self.name}_pk')) def _build_regular_column(self, field_: sql.EntityField, sqlalchemy_type: Type): args = [field_.name, sqlalchemy_type] kwargs = {} if field_.is_pk(): self._pks.append(field_.name) self.columns.append(Column(*args, **kwargs)) def _needs_relation_column(self, field_: sql.EntityField): r = self._find_relationship(field_) if r.type == sql.Relationship.ONE_TO_ONE and not r.needs_uselist: return False return not field_.is_list() and r.type != sql.Relationship.MANY_TO_MANY def _build_relation_column(self, field_: sql.EntityField): name = f'{field_.name}_id' setattr(self.entity.entity, name, None) relationship = self._find_relationship(field_) table_name = inflection.tableize(relationship.entity_b.entity.__name__) self.columns.append( Column( name, String(length=36), ForeignKey( f'{table_name}.{relationship.entity_b.primary_key_column.name}' ))) def _find_relationship(self, field_: sql.EntityField): for relationship in self.relationships: if relationship.field_a == field_: return relationship raise RuntimeError( f'Could not find relationship for field {field_.name}')
class AuthorizationCode(ff.Entity): client: Client = ff.required() user: User = ff.required() scopes: List[str] = ff.required() redirect_uri: str = ff.optional() code: str = ff.required(str, length=36) expires_at: datetime = ff.required() challenge: str = ff.optional(str, length=128) challenge_method: str = ff.optional(str, length=6)
class Condition(ff.ValueObject): name: str = ff.required() operator: str = ff.required(validators=ff.IsOneOf(( 'equal_to', 'equal_to_case_insensitive', 'starts_with', 'ends_with', 'contains', 'matches_regex', 'non_empty', 'greater_than', 'greater_than_or_equal_to', 'less_than', 'less_than_or_equal_to', 'is_true', 'is_false', 'does_not_contain', 'contains_all', 'is_contained_by', 'shares_at_least_one_element_with', 'shares_exactly_one_element_with', 'shares_no_elements_with'))) value: str = ff.required()
class Grant(ff.AggregateRoot): id: str = ff.id_() client_id: str = ff.required(str) user_id: str = ff.required(str) code: str = ff.required(str) redirect_uri: str = ff.required(str) scopes: List[str] = ff.list_() expires: datetime = ff.required(datetime) def validate_redirect_uri(self, redirect_uri: str): return self.redirect_uri == redirect_uri
class Task(ff.Entity): id: str = ff.id_() name: str = ff.required() due_date: datetime = ff.required() complete: bool = ff.optional(default=False) def complete_task(self): self.complete = True def is_overdue(self): return datetime.now() >= self.due_date
class Audience(ff.AggregateRoot): id: str = ff.id_() name: str = ff.required() tenant: domain.Tenant = ff.required() campaigns: List[domain.Campaign] = ff.list_() services: list = ff.list_(validators=ff.IsOneOf((MAILCHIMP, ))) meta: dict = ff.dict_() def get_campaign(self, id_: str) -> Optional[domain.Campaign]: for campaign in self.campaigns: if campaign.id == id_: return campaign
class JoinTable: relationship: sql.Relationship = ff.required() name: str = None columns: List[Column] = ff.list_() sql_table: Table = None def __post_init__(self): self._initialize() def _initialize(self): terms = [ inflection.tableize(self.relationship.entity_a.entity.__name__), inflection.tableize(self.relationship.entity_b.entity.__name__), ] terms.sort() self.name = f'{terms[0]}_{terms[1]}' self.columns.append( Column(self.relationship.entity_a.foreign_id_column_name, String(length=36), ForeignKey(self.relationship.entity_a.fk_column_string))) self.columns.append( Column(self.relationship.entity_b.foreign_id_column_name, String(length=36), ForeignKey(self.relationship.entity_b.fk_column_string))) def __eq__(self, other): if isinstance(other, str): return other == self.name return other.name == self.name def __hash__(self): return hash(self.name)
class TodoList(ff.AggregateRoot, create_on='iam.UserCreated', delete_on='iam.UserDeleted'): id: str = ff.id_() user: User = ff.required() name: str = ff.optional() tasks: List[Task] = ff.list_() def __post_init__(self): if self.name is None: self.name = f"{self.user.name}'s TODO List" @ff.rest('/task', method='POST') def add_task(self, task: Task) -> ff.EventList: self.tasks.append(task) return 'TaskAdded', task def remove_task(self, task: Task): self.tasks.remove(task) def complete_task(self, task_id: str) -> ff.EventList: for task in self.tasks: if task_id == task.id: task.complete_task() return 'TaskCompleted', task raise Exception(f'Task {task_id} not found in TodoList {self}')
class Widget(ff.AggregateRoot): id: str = ff.pk() name: str = ff.required() addresses: List[Address] = ff.list_() category: Category = None part: Part = None priority: int = ff.optional() deleted: bool = ff.optional(default=False)
class BearerToken(ff.Entity): client: Client = ff.required() user: User = ff.required() scopes: List[str] = ff.required() access_token: str = ff.required(str, length=36) refresh_token: str = ff.required(str, length=36) expires_at: datetime = ff.required()
class Role(ff.AggregateRoot): id: str = ff.id_() name: str = ff.required(str) users: List[str] = ff.list_() def assign_role_to_user(self, user_id: str): if user_id not in self.users: self.users.append(user_id) return 'iam.RoleAssigned', {'user_id': user_id, 'role_id': self.id} def remove_role_from_user(self, user_id: str): if user_id in self.users: self.users.remove(user_id) return 'iam.RoleRemoved', {'user_id': user_id, 'role_id': self.id}
class Campaign(ff.Entity): id: str = ff.id_() name: str = ff.required() members: List[domain.AudienceMember] = ff.list_() def get_member_by_contact_id(self, contact_id: str): for member in self.members: if member.contact.id == contact_id: return member def add_contact(self, contact: domain.Contact, **kwargs): if self.get_member_by_contact_id(contact.id) is None: kwargs.update({'contact': contact}) self.members.append( domain.AudienceMember( **ff.build_argument_list(kwargs, domain.AudienceMember)))
class Group(ff.AggregateRoot): id: str = ff.id_() name: str = ff.required(str) users: List[str] = ff.list_() roles: List[str] = ff.list_() def assign_user_to_group(self, user_id: str): if user_id not in self.users: self.users.append(user_id) return 'iam.GroupAssigned', {'user_id': user_id, 'group_id': self.id} def remove_user_from_group(self, user_id: str): if user_id in self.users: self.users.remove(user_id) return 'iam.GroupRemoved', {'user_id': user_id, 'group_id': self.id}
class User(ff.AggregateRoot): id: str = ff.id_() name: str = ff.required() email: str = ff.required()
class Command(ff.ValueObject): id: str = ff.id_() name: str = ff.required(index=True) params: dict = ff.dict_()
class AuthenticationFailed(IaaaEvent): user_id: str = ff.required()
class Entity: entity: Type[E] = ff.required() fields: List[sql.EntityField] = ff.list_() def __post_init__(self): annotations_ = get_type_hints(self.entity) for field_ in fields(self.entity): self.fields.append( sql.EntityField(entity=self, field=field_, annotations=annotations_)) @property def type(self): return self.entity @property def primary_key_column(self): for field_ in self.fields: if field_.is_pk(): return field_ @property def table_name(self): return inflection.tableize(self.entity.__name__) @property def fk_column_string(self): return f'{self.table_name}.{self.primary_key_column.name}' @property def foreign_id_column_name(self): return f'{self.entity.__name__.lower()}_id' def get_field(self, name: str): for f in self.fields: if f.name == name: return f def get_relationship_fields(self) -> List[sql.EntityField]: ret = [] for field_ in self.fields: if field_.sqlalchemy_type is None: ret.append(field_) return ret def add_id_column(self, field_: sql.EntityField, foreign_entity: sql.Entity): name = f'{field_.name}_id' setattr(self.entity, name, None) def __eq__(self, other): try: if issubclass(other, ff.Entity): return self.entity == other except TypeError: pass try: if isinstance(other, sql.Entity): return self.entity == other.entity except TypeError: pass return False
class ChildWidget(ff.ValueObject): name: str = ff.required() extra1: str = ff.optional() extra2: str = ff.optional()
class AudienceMember(ff.AggregateRoot): id: str = ff.id_() audience: str = ff.required() contact: str = ff.required() tags: List[str] = ff.list_() meta: dict = ff.dict_()
class ThisEvent(Event): foo: str = required()
class Event(ff.Entity): id: str = ff.id_() name: str = ff.required() reminders: List[cal.Reminder] = ff.list_()
class Relationship: ONE_TO_ONE = 1 ONE_TO_MANY = 2 MANY_TO_ONE = 3 MANY_TO_MANY = 4 entity_a: sql.Entity = ff.required() field_a: sql.EntityField = ff.required() entity_b: sql.Entity = ff.required() field_b: sql.EntityField = ff.optional() type: int = None _join_table: sql.JoinTable = None def __post_init__(self): self._initialize() def _initialize(self): has_many = False found_inverse = False for field_ in self.entity_b.get_relationship_fields(): if field_.type() == self.entity_a.type: self.field_b = field_ found_inverse = True if field_.is_list(): has_many = True break if self.field_a.is_list(): if has_many: self.type = self.MANY_TO_MANY elif found_inverse: self.type = self.ONE_TO_MANY else: self.entity_a.add_id_column(self.field_a, self.entity_b) if has_many: self.type = self.MANY_TO_ONE elif found_inverse: self.type = self.ONE_TO_ONE if self.type is None: raise RuntimeError('Could not determine relationship type') @property def is_bidirectional(self): return self.field_b is not None @property def join_table(self): return self._join_table @join_table.setter def join_table(self, value: sql.JoinTable): if self.type != self.MANY_TO_MANY: raise sql.MappingError( 'Many to many relationships do not use a join table') self._join_table = value @property def needs_uselist(self): fields = [self.field_a.name, self.field_b.name] fields.sort() return self.field_a.name == fields[0]
class ParentWidget(ff.AggregateRoot): id: str = ff.id_() name: str = ff.required() child: ChildWidget = ff.optional()
class Role(ff.AggregateRoot): id: str = ff.id_() name: str = ff.required(length=255, index=True) scopes: List[Scope] = ff.list_()
class Reminder(ff.Entity): id: str = ff.id_() event: cal.Event = ff.required()
class Rule(ff.ValueObject): conditions: ConditionSet = ff.required() commands: List[Command] = ff.list_()
class RuleSet(ff.AggregateRoot): id: str = ff.id_() name: str = ff.optional(index=True) rules: List[Rule] = ff.required()
class User(ff.AggregateRoot): id: str = ff.id_() name: str = ff.required() email: str = ff.required(index=True) roles: List[Role] = ff.list_() special_role: Role = ff.optional()