class BaseSyllable(db.Model, InitBase, DBBase): """Base Syllable's DB Model Describes a table structure for storing information about loglan syllables. <details><summary>Show Examples</summary><p> ```python {'id': 37, 'name': 'zv', 'type': 'InitialCC', 'allowed': True} {'id': 38, 'name': 'cdz', 'type': 'UnintelligibleCCC', 'allowed': False} ``` </p></details> """ __tablename__ = t_name_syllables id = db.Column(db.Integer, primary_key=True) """*Syllable's internal ID number* **int** : primary_key=True""" name = db.Column(db.String(8), nullable=False, unique=False) """*Syllable itself* **str** : max_length=8, nullable=False, unique=False""" type = db.Column(db.String(32), nullable=False, unique=False) """*Syllable's type* **str** : max_length=8, nullable=False, unique=False""" allowed = db.Column(db.Boolean, nullable=True, unique=False) """*Is this syllable acceptable in grammar*
class BaseAuthor(db.Model, InitBase, DBBase): """Base Author's DB Model Describes a table structure for storing information about words authors. Connects with words with "many-to-many" relationship. See `t_connect_authors`. <details><summary>Show Examples</summary><p> ```python {'id': 13, 'full_name': 'James Cooke Brown', 'abbreviation': 'JCB', 'notes': ''} {'id': 29, 'full_name': 'Loglan 4&5', 'abbreviation': 'L4', 'notes': 'The printed-on-paper book, 1975 version of the dictionary.'} ``` </p></details> """ __tablename__ = t_name_authors id = db.Column(db.Integer, primary_key=True) """*Author's internal ID number* **int** : primary_key=True""" abbreviation = db.Column(db.String(64), nullable=False, unique=True) """*Author's abbreviation (used in the LOD dictionary)* **str** : max_length=64, nullable=False, unique=True Example: > JCB, L4 """ full_name = db.Column(db.String(64), nullable=True, unique=False) """ *Author's full name (if exists)* **str** : max_length=64, nullable=True, unique=False Example: > James Cooke Brown, Loglan 4&5 """ notes = db.Column(db.String(128), nullable=True, unique=False) """*Any additional information about author* **str** : max_length=128, nullable=True, unique=False """ _contribution = db.relationship("BaseWord", back_populates="_authors", secondary=t_connect_authors) @property def contribution(self): """ *Relationship query for getting a list of words coined by this author* **query** : Optional[List[BaseWord]] """ return self._contribution
class BaseSetting(db.Model, InitBase, DBBase): """Base Setting's DB Model Describes a table structure for storing dictionary settings. <details><summary>Show Examples</summary><p> ```python {'id': 1, 'last_word_id': 10141, 'date': datetime.datetime(2020, 10, 25, 5, 10, 20), 'db_release': '4.5.9', 'db_version': 2} ``` </p></details> """ __tablename__ = t_name_settings id = db.Column(db.Integer, primary_key=True) """*Setting's internal ID number* **int** : primary_key=True""" date = db.Column(db.DateTime, nullable=True, unique=False) """*Last modified date* **dateime.datetime** : nullable=True, unique=False""" db_version = db.Column(db.Integer, nullable=False, unique=False) """*Database version (for old application)* **int** : nullable=False, unique=False""" last_word_id = db.Column(db.Integer, nullable=False, unique=False) """*ID number of the last word in DB* **int** : nullable=False, unique=False""" db_release = db.Column(db.String(16), nullable=False) """*Database release (for new application)*
class BaseKey(db.Model, InitBase, DBBase): """Base Key's DB Model Describes a table structure for storing information about key words of the word's definitions. Some key words could belong to many definitions and some definitions could have many key words. That's why the relationship between Key and Definition should be many-to-many. See `t_connect_keys`. There is additional `word_language` UniqueConstraint here. <details><summary>Show Examples</summary><p> ```python {'language': 'en', 'word': 'aura', 'id': 1234} {'language': 'en', 'word': 'emotionality', 'id': 4321} ``` </p></details> """ __tablename__ = t_name_keys __table_args__ = (db.UniqueConstraint('word', 'language', name='_word_language_uc'), ) id = db.Column(db.Integer, primary_key=True) """*Key's internal ID number* **int** : primary_key=True""" word = db.Column(db.String(64), nullable=False, unique=False) """*Key's vernacular word* **str** : max_length=64, nullable=False, unique=False It is non-unique, as words can be the same in spelling in different languages""" language = db.Column(db.String(16), nullable=False, unique=False) """*Key's language* **str** : max_length=16, nullable=False, unique=False""" _definitions = db.relationship("BaseDefinition", secondary=t_connect_keys, lazy='dynamic', back_populates="_keys") @property def definitions(self): return self._definitions
class BaseType(db.Model, InitBase, DBBase): """BaseType model""" __tablename__ = t_name_types id = db.Column(db.Integer, primary_key=True) """Type's internal ID number: Integer - E.g. 4, 8""" type = db.Column(db.String(16), nullable=False) # E.g. 2-Cpx, C-Prim type_x = db.Column(db.String(16), nullable=False) # E.g. Predicate, Predicate group = db.Column(db.String(16)) # E.g. Cpx, Prim parentable = db.Column(db.Boolean, nullable=False) # E.g. True, False description = db.Column(db.String(255)) # E.g. Two-term Complex, ... _words = db.relationship("BaseWord", back_populates="_type", foreign_keys="BaseWord.type_id") @property def words(self): return self._words @classmethod def by(cls, type_filter: Union[str, List[str]]) -> BaseQuery: """ Args: type_filter: Union[str, List[str]]: Returns: """ type_filter = [ type_filter, ] if isinstance(type_filter, str) else type_filter return cls.query.filter( or_( cls.type.in_(type_filter), cls.type_x.in_(type_filter), cls.group.in_(type_filter), ))
class BaseWord(db.Model, InitBase, DBBase): """BaseWord model""" __tablename__ = t_name_words id = db.Column(db.Integer, primary_key=True) """Word's internal ID number: Integer""" name = db.Column(db.String(64), nullable=False) origin = db.Column(db.String(128)) origin_x = db.Column(db.String(64)) match = db.Column(db.String(8)) rank = db.Column(db.String(8)) year = db.Column(db.Date) notes = db.Column(db.JSON) # Fields for legacy database compatibility id_old = db.Column(db.Integer, nullable=False) TID_old = db.Column(db.Integer) # references # Relationships type_id = db.Column("type", db.ForeignKey(f'{t_name_types}.id'), nullable=False) _type: BaseType = db.relationship(BaseType.__name__, back_populates="_words") event_start_id = db.Column("event_start", db.ForeignKey(f'{t_name_events}.id'), nullable=False) _event_start: BaseEvent = db.relationship(BaseEvent.__name__, foreign_keys=[event_start_id], back_populates="_appeared_words") event_end_id = db.Column("event_end", db.ForeignKey(f'{t_name_events}.id')) _event_end: BaseEvent = db.relationship(BaseEvent.__name__, foreign_keys=[event_end_id], back_populates="_deprecated_words") _authors: BaseQuery = db.relationship(BaseAuthor.__name__, secondary=t_connect_authors, back_populates="_contribution", lazy='dynamic', enable_typechecks=False) _definitions: BaseQuery = db.relationship(BaseDefinition.__name__, back_populates="_source_word", lazy='dynamic') # word's derivatives _derivatives = db.relationship( 'BaseWord', secondary=t_connect_words, primaryjoin=(t_connect_words.c.parent_id == id), secondaryjoin=(t_connect_words.c.child_id == id), backref=db.backref('_parents', lazy='dynamic', enable_typechecks=False), lazy='dynamic', enable_typechecks=False) @property def type(self) -> BaseQuery: return self._type @property def event_start(self) -> BaseQuery: return self._event_start @property def event_end(self) -> BaseQuery: return self._event_end @property def authors(self) -> BaseQuery: return self._authors @property def definitions(self) -> BaseQuery: return self._definitions @property def derivatives(self) -> BaseQuery: return self._derivatives def query_derivatives(self, word_type: str = None, word_type_x: str = None, word_group: str = None) -> BaseQuery: """Query to get all derivatives of the word, depending on its parameters Args: word_type: str: (Default value = None) E.g. "2-Cpx", "C-Prim", "LW"<hr> word_type_x: str: (Default value = None) E.g. "Predicate", "Name", "Affix"<hr> word_group: str: (Default value = None) E.g. "Cpx", "Prim", "Little"<hr> Returns: BaseQuery """ type_values = [ (BaseType.type, word_type), (BaseType.type_x, word_type_x), (BaseType.group, word_group), ] type_filters = [i[0] == i[1] for i in type_values if i[1]] return self._derivatives.join(BaseType)\ .filter(self.id == t_connect_words.c.parent_id, *type_filters)\ .order_by(type(self).name.asc()) @property def parents(self) -> BaseQuery: """Query to get all parents for Complexes, Little words or Affixes Returns: BaseQuery """ return self._parents @property def complexes(self) -> BaseQuery: """ Get all word's complexes if exist Only primitives and Little Words have complexes. Returns: BaseQuery """ return self.query_derivatives(word_group="Cpx") @property def affixes(self) -> BaseQuery: """ Get all word's affixes if exist Only primitives have affixes. Returns: BaseQuery """ return self.query_derivatives(word_type="Afx") @property def keys(self) -> BaseQuery: """Get all BaseKey object related to this BaseWord. Keep in mind that duplicated keys from related definitions will be counted with ```.count()``` but excluded from ```.all()``` request Returns: BaseQuery """ return BaseKey.query.join(t_connect_keys, BaseDefinition, BaseWord).filter(BaseWord.id == self.id)
class BaseEvent(db.Model, InitBase, DBBase): """Base Event's DB Model Describes a table structure for storing information about lexical events. <details><summary>Show Examples</summary><p> ```python {'suffix': 'INIT', 'definition': 'The initial vocabulary before updates.', 'date': datetime.date(1975, 1, 1), 'annotation': 'Initial', 'name': 'Start', 'id': 1} {'suffix': 'RDC', 'definition': 'parsed all the words in the dictionary, identified ones that the parser did not recognize as words', 'date': datetime.date(2016, 1, 15), 'annotation': 'Randall Cleanup', 'name': 'Randall Dictionary Cleanup', 'id': 5} ``` </p></details> """ __tablename__ = t_name_events id = db.Column(db.Integer, primary_key=True) """*Event's internal ID number* **int** : primary_key=True""" date = db.Column(db.Date, nullable=False, unique=False) """*Event's starting day* **dateime.date** : nullable=False, unique=False""" name = db.Column(db.String(64), nullable=False, unique=False) """*Event's short name* **str** : max_length=64, nullable=False, unique=False""" definition = db.Column(db.Text, nullable=False, unique=False) """*Event's definition* **str** : nullable=False, unique=False""" annotation = db.Column(db.String(16), nullable=False, unique=False) """*Event's annotation (displayed in old format dictionary HTML file)* **str** : max_length=16, nullable=False, unique=False""" suffix = db.Column(db.String(16), nullable=False, unique=False) """*Event's suffix (used to create filename when exporting HTML file)* **str** : max_length=16, nullable=False, unique=False""" _deprecated_words = db.relationship("BaseWord", back_populates="_event_end", foreign_keys="BaseWord.event_end_id") _appeared_words = db.relationship("BaseWord", back_populates="_event_start", foreign_keys="BaseWord.event_start_id") @property def deprecated_words(self): """ *Relationship query for getting a list of words deprecated during this event* **query** : Optional[List[BaseWord]]""" return self._deprecated_words @property def appeared_words(self): """ *Relationship query for getting a list of words appeared during this event* **query** : Optional[List[BaseWord]]""" return self._appeared_words @classmethod def latest(cls) -> BaseEvent: """ Gets the latest (current) `BaseEvent` from DB """ return cls.query.order_by(-cls.id).first()
class BaseDefinition(db.Model, InitBase, DBBase): """BaseDefinition model""" __tablename__ = t_name_definitions id = db.Column(db.Integer, primary_key=True) """Definition's internal ID number: Integer""" word_id = db.Column(db.Integer, db.ForeignKey(f'{t_name_words}.id'), nullable=False) position = db.Column(db.Integer, nullable=False) usage = db.Column(db.String(64)) grammar_code = db.Column(db.String(8)) slots = db.Column(db.Integer) case_tags = db.Column(db.String(16)) body = db.Column(db.Text, nullable=False) language = db.Column(db.String(16)) notes = db.Column(db.String(255)) APPROVED_CASE_TAGS = [ "B", "C", "D", "F", "G", "J", "K", "N", "P", "S", "V", ] KEY_PATTERN = r"(?<=\«)(.+?)(?=\»)" _keys = db.relationship(BaseKey.__name__, secondary=t_connect_keys, back_populates="_definitions", lazy='dynamic') _source_word = db.relationship("BaseWord", back_populates="_definitions") @property def keys(self) -> BaseQuery: """ Returns: """ return self._keys @property def source_word(self) -> BaseQuery: """ Returns: """ return self._source_word @property def grammar(self) -> str: """ Combine definition's 'slots' and 'grammar_code' attributes Returns: String with grammar data like (3v) or (2n) """ return f"({self.slots if self.slots else ''}" \ f"{self.grammar_code if self.grammar_code else ''})" def link_keys_from_list_of_str(self, source: List[str], language: str = None) -> List[BaseKey]: """Linking a list of vernacular words with BaseDefinition Only new words will be linked, skipping those that were previously linked Args: source: List[str]: List of words on vernacular language language: str: Language of source words (Default value = None) Returns: List of linked BaseKey objects """ language = language if language else self.language new_keys = BaseKey.query.filter( BaseKey.word.in_(source), BaseKey.language == language, ~exists().where(BaseKey.id == self.keys.subquery().c.id), ).all() self.keys.extend(new_keys) return new_keys def link_key_from_str(self, word: str, language: str = None) -> Optional[BaseKey]: """Linking vernacular word with BaseDefinition object Only new word will be linked, skipping this that was previously linked Args: word: str: name of BaseWord on vernacular language language: str: BaseWord's language (Default value = None) Returns: Linked BaseKey object or None if it were already linked """ language = language if language else self.language result = self.link_keys_from_list_of_str(source=[ word, ], language=language) return result[0] if result else None def link_keys_from_definition_body( self, language: str = None, pattern: str = KEY_PATTERN) -> List[BaseKey]: """Extract and link keys from BaseDefinition's body Args: language: str: Language of BaseDefinition's keys (Default value = None) pattern: str: Regex pattern for extracting keys from the BaseDefinition's body (Default value = KEY_PATTERN) Returns: List of linked BaseKey objects """ language = language if language else self.language keys = re.findall(pattern, self.body) return self.link_keys_from_list_of_str(source=keys, language=language) def link_keys( self, source: Union[List[str], str, None] = None, language: str = None, pattern: str = KEY_PATTERN) -> Union[BaseKey, List[BaseKey], None]: """Universal method for linking all available types of key sources with BaseDefinition Args: source: Union[List[str], str, None]: If no source is provided, keys will be extracted from the BaseDefinition's body If source is a string or a list of strings, the language of the keys must be specified TypeError will be raised if the source contains inappropriate data (Default value = None) language: str: Language of BaseDefinition's keys (Default value = None) pattern: str: Regex pattern for extracting keys from the BaseDefinition's body (Default value = KEY_PATTERN) Returns: None, BaseKey, or List of BaseKeys """ def is_list_of_str(src: list) -> bool: checked_items = all(isinstance(item, str) for item in src) return bool(isinstance(src, list) and checked_items) language = language if language else self.language if not source: return self.link_keys_from_definition_body(language=language, pattern=pattern) if isinstance(source, str): return self.link_key_from_str(word=source, language=language) if is_list_of_str(source): return self.link_keys_from_list_of_str(source=source, language=language) raise TypeError( "Source for keys should have a string, or list of strings." "You input %s" % type(source)) @classmethod def by_key(cls, key: Union[BaseKey, str], language: str = None, case_sensitive: bool = False) -> BaseQuery: """Definition.Query filtered by specified key Args: key: Union[BaseKey, str]: language: str: Language of key (Default value = None) case_sensitive: bool: (Default value = False) Returns: BaseQuery """ key = (BaseKey.word if isinstance(key, BaseKey) else str(key)).replace( "*", "%") request = cls.query.join(t_connect_keys, BaseKey).order_by(BaseKey.word) if language: request = request.filter(BaseKey.language == language) return request.filter( BaseKey.word.like(key) if case_sensitive else BaseKey.word. ilike(key))