Passed
Push — main ( 46dff7...b74274 )
by torrua
01:45
created

BaseDefinition.keys()   A

Complexity

Conditions 1

Size

Total Lines 8
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

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