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 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 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 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 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 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 Contact(ff.AggregateRoot): id: str = ff.id_() sub: str = ff.optional(index=True) email: str = ff.optional(index=True) given_name: str = ff.optional() family_name: str = ff.optional() birthdate: date = ff.optional() deleted_on: datetime = ff.optional()
class Foo(BaseClass): foo_field: str = ff.optional()
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 User(ff.AggregateRoot): # OpenID standard fields sub: str = ff.id_(validators=[ff.HasLength(36)]) name: str = ff.optional(str) given_name: str = ff.optional(str) family_name: str = ff.optional(str) middle_name: str = ff.optional(str) nickname: str = ff.optional(str) preferred_username: str = ff.optional(str) profile: str = ff.optional(str) picture: str = ff.optional(str) website: str = ff.optional(str) email: str = ff.optional(str, validators=[ff.IsValidEmail()]) email_verified: bool = ff.optional(bool, default=False) gender: str = ff.optional(str, validators=[ff.IsOneOf(('Male', 'Female'))]) birthdate: date = ff.optional(date) zoneinfo: str = ff.optional(str) locale: str = ff.optional(str) phone_number: str = ff.optional(str) phone_number_verified: bool = ff.optional(bool, default=False) address: Address = ff.optional(Address) updated_at: datetime = ff.now() # Custom fields created_at: datetime = ff.now() deleted_at: datetime = ff.optional(datetime) password_hash: str = ff.optional(str, length=32) salt: str = ff.hidden() # __pragma__('skip') @classmethod def create(cls, **kwargs): if 'email' in kwargs: kwargs['email'] = str(kwargs['email']).lower() try: kwargs['salt'] = bcrypt.gensalt() kwargs['password_hash'] = User._hash_password(kwargs['password'], kwargs['salt']) except KeyError: raise ff.MissingArgument('password is a required field for User::create()') return cls(**ff.build_argument_list(kwargs, cls)) @classmethod def _hash_password(cls, password: str, salt: str): return bcrypt.hashpw(password.encode('utf-8'), salt).decode('utf-8') def correct_password(self, password: str): return self.password_hash == User._hash_password(password, self.salt)
class Widget(ff.AggregateRoot): id: str = ff.id_() name: str = ff.optional() value: int = ff.optional()
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()
class ChildWidget(ff.ValueObject): name: str = ff.required() extra1: str = ff.optional() extra2: str = ff.optional()
class ParentWidget(ff.AggregateRoot): id: str = ff.id_() name: str = ff.required() child: ChildWidget = ff.optional()
class Widget2(ff.AggregateRoot): foo: BaseClass = ff.optional()
class Config(ff.ValueObject): x: str = ff.optional() y: str = ff.optional() z: str = ff.optional()
class ConditionSet(ff.ValueObject): all: bool = ff.optional(default=True) conditions: List[Condition] = ff.list_() sub_conditions: List[ConditionSet] = ff.list_()
class BaseClass(ff.ValueObject): base_field: str = ff.optional()
class Widget(ff.AggregateRoot): name: str = ff.optional() config: Config = ff.optional()
class RuleSet(ff.AggregateRoot): id: str = ff.id_() name: str = ff.optional(index=True) rules: List[Rule] = ff.required()
class Bar(BaseClass): bar_field: str = ff.optional()