Passed
Push — main ( 4cd557...ac26d8 )
by torrua
01:36
created

loglan_db.model_db.base_definition   A

Complexity

Total Complexity 20

Size/Duplication

Total Lines 181
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
eloc 79
dl 0
loc 181
ccs 63
cts 63
cp 1
rs 10
c 0
b 0
f 0
wmc 20

6 Methods

Rating   Name   Duplication   Size   Complexity  
A BaseDefinition.grammar() 0 9 1
A BaseDefinition.link_keys_from_definition_body() 0 17 2
A BaseDefinition.link_keys_from_list_of_str() 0 25 2
B BaseDefinition.link_keys() 0 33 6
A BaseDefinition.link_key_from_str() 0 15 3
B BaseDefinition.by_key() 0 35 6
1
# -*- coding: utf-8 -*-
2 1
"""
3
This module contains a basic Definition Model
4
"""
5 1
import re
6 1
from typing import List, Optional, Union
7
8 1
from flask_sqlalchemy import BaseQuery
9 1
from sqlalchemy import exists
10
11 1
from loglan_db.model_db import t_name_definitions, t_name_words
12 1
from loglan_db.model_db.base_connect_tables import t_connect_keys
13 1
from loglan_db.model_db.base_key import BaseKey
14 1
from loglan_db.model_db.base_word import db
15 1
from loglan_db.model_init import InitBase, DBBase
16
17
18 1
class BaseDefinition(db.Model, InitBase, DBBase):
19
    """BaseDefinition model"""
20 1
    __tablename__ = t_name_definitions
21
22 1
    id = db.Column(db.Integer, primary_key=True)
23
    """Definition's internal ID number: Integer"""
24
25 1
    word_id = db.Column(db.Integer, db.ForeignKey(f'{t_name_words}.id'), nullable=False)
26 1
    position = db.Column(db.Integer, nullable=False)
27 1
    usage = db.Column(db.String(64))
28 1
    grammar_code = db.Column(db.String(8))
29 1
    slots = db.Column(db.Integer)
30 1
    case_tags = db.Column(db.String(16))
31 1
    body = db.Column(db.Text, nullable=False)
32 1
    language = db.Column(db.String(16))
33 1
    notes = db.Column(db.String(255))
34
35 1
    APPROVED_CASE_TAGS = ["B", "C", "D", "F", "G", "J", "K", "N", "P", "S", "V", ]
36 1
    KEY_PATTERN = r"(?<=\«)(.+?)(?=\»)"
37
38 1
    keys = db.relationship(BaseKey.__name__, secondary=t_connect_keys,
39
                           backref="definitions", lazy='dynamic', enable_typechecks=False)
40
41 1
    @property
42 1
    def grammar(self) -> str:
43
        """
44
        Combine definition's 'slots' and 'grammar_code' attributes
45
46
        Returns:
47
            String with grammar data like (3v) or (2n)
48
        """
49 1
        return f"({self.slots if self.slots else ''}" \
50
               f"{self.grammar_code if self.grammar_code else ''})"
51
52 1
    def link_keys_from_list_of_str(
53
            self, source: List[str],
54
            language: str = None) -> List[BaseKey]:
55
        """Linking a list of vernacular words with BaseDefinition
56
        Only new words will be linked, skipping those that were previously linked
57
58
        Args:
59
          source: List[str]: List of words on vernacular language
60
          language: str: Language of source words (Default value = None)
61
62
        Returns:
63
          List of linked BaseKey objects
64
65
        """
66
67 1
        language = language if language else self.language
68
69 1
        new_keys = BaseKey.query.filter(
70
            BaseKey.word.in_(source),
71
            BaseKey.language == language,
72
            ~exists().where(BaseKey.id == self.keys.subquery().c.id),
73
        ).all()
74
75 1
        self.keys.extend(new_keys)
76 1
        return new_keys
77
78 1
    def link_key_from_str(self, word: str, language: str = None) -> Optional[BaseKey]:
79
        """Linking vernacular word with BaseDefinition object
80
        Only new word will be linked, skipping this that was previously linked
81
82
        Args:
83
          word: str: name of BaseWord on vernacular language
84
          language: str: BaseWord's language (Default value = None)
85
86
        Returns:
87
          Linked BaseKey object or None if it were already linked
88
89
        """
90 1
        language = language if language else self.language
91 1
        result = self.link_keys_from_list_of_str(source=[word, ], language=language)
92 1
        return result[0] if result else None
93
94 1
    def link_keys_from_definition_body(
95
            self, language: str = None,
96
            pattern: str = KEY_PATTERN) -> List[BaseKey]:
97
        """Extract and link keys from BaseDefinition's body
98
99
        Args:
100
          language: str: Language of BaseDefinition's keys (Default value = None)
101
          pattern: str: Regex pattern for extracting keys from the BaseDefinition's body
102
            (Default value = KEY_PATTERN)
103
104
        Returns:
105
          List of linked BaseKey objects
106
107
        """
108 1
        language = language if language else self.language
109 1
        keys = re.findall(pattern, self.body)
110 1
        return self.link_keys_from_list_of_str(source=keys, language=language)
111
112 1
    def link_keys(
113
            self, source: Union[List[str], str, None] = None,
114
            language: str = None, pattern: str = KEY_PATTERN) -> Union[BaseKey, List[BaseKey]]:
115
        """Universal method for linking all available types of key sources with BaseDefinition
116
117
        Args:
118
          source: Union[List[str], str, None]:
119
            If no source is provided, keys will be extracted from the BaseDefinition's body
120
            If source is a string or a list of strings, the language of the keys must be specified
121
            TypeError will be raised if the source contains inappropriate data
122
            (Default value = None)
123
          language: str: Language of BaseDefinition's keys (Default value = None)
124
          pattern: str: Regex pattern for extracting keys from the BaseDefinition's body
125
            (Default value = KEY_PATTERN)
126
127
        Returns:
128
          None, BaseKey, or List of BaseKeys
129
130
        """
131
132 1
        language = language if language else self.language
133
134 1
        if not source:
135 1
            return self.link_keys_from_definition_body(language=language, pattern=pattern)
136
137 1
        if isinstance(source, str):
138 1
            return self.link_key_from_str(word=source, language=language)
139
140 1
        if isinstance(source, list) and all(isinstance(item, str) for item in source):
141 1
            return self.link_keys_from_list_of_str(source=source, language=language)
142
143 1
        raise TypeError("Source for keys should have a string, or list of strings."
144
                        "You input %s" % type(source))
145
146 1
    @classmethod
147 1
    def by_key(
148
            cls, key: Union[BaseKey, str],
149
            language: str = None,
150
            case_sensitive: bool = False,
151
            partial_results: bool = False,
152
    ) -> BaseQuery:
153
        """Definition.Query filtered by specified key
154
155
        Args:
156
          key: Union[BaseKey, str]:
157
          language: str: Language of key (Default value = None)
158
          case_sensitive: bool:  (Default value = False)
159
          partial_results: bool:  (Default value = False)
160
161
        Returns:
162
          BaseQuery
163
164
        """
165
166 1
        key = BaseKey.word if isinstance(key, BaseKey) else str(key)
167 1
        request = cls.query.join(t_connect_keys, BaseKey).order_by(BaseKey.word)
168
169 1
        if language:
170 1
            request = request.filter(BaseKey.language == language)
171
172 1
        if case_sensitive:
173 1
            if partial_results:
174 1
                return request.filter(BaseKey.word.like(f"{key}%"))
175 1
            return request.filter(BaseKey.word == key)
176
177 1
        if partial_results:
178 1
            return request.filter(BaseKey.word.ilike(f"{key}%"))
179
180
        return request.filter(BaseKey.word.ilike(key))
181