Passed
Push — main ( 6335c3...d4deb7 )
by torrua
01:49
created

BaseDefinition.link_keys()   A

Complexity

Conditions 5

Size

Total Lines 37
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 5

Importance

Changes 0
Metric Value
cc 5
eloc 15
nop 4
dl 0
loc 37
ccs 12
cts 12
cp 1
crap 5
rs 9.1832
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 1
__pdoc__ = {
18
    'BaseDefinition.source_word': 'source_word',
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(BaseKey.__name__, secondary=t_connect_keys,
44
                           backref="definitions", lazy='dynamic', enable_typechecks=False)
45
46 1
    @property
47 1
    def grammar(self) -> str:
48
        """
49
        Combine definition's 'slots' and 'grammar_code' attributes
50
51
        Returns:
52
            String with grammar data like (3v) or (2n)
53
        """
54 1
        return f"({self.slots if self.slots else ''}" \
55
               f"{self.grammar_code if self.grammar_code else ''})"
56
57 1
    def link_keys_from_list_of_str(
58
            self, source: List[str],
59
            language: str = None) -> List[BaseKey]:
60
        """Linking a list of vernacular words with BaseDefinition
61
        Only new words will be linked, skipping those that were previously linked
62
63
        Args:
64
          source: List[str]: List of words on vernacular language
65
          language: str: Language of source words (Default value = None)
66
67
        Returns:
68
          List of linked BaseKey objects
69
70
        """
71
72 1
        language = language if language else self.language
73
74 1
        new_keys = BaseKey.query.filter(
75
            BaseKey.word.in_(source),
76
            BaseKey.language == language,
77
            ~exists().where(BaseKey.id == self.keys.subquery().c.id),
78
        ).all()
79
80 1
        self.keys.extend(new_keys)
81 1
        return new_keys
82
83 1
    def link_key_from_str(self, word: str, language: str = None) -> Optional[BaseKey]:
84
        """Linking vernacular word with BaseDefinition object
85
        Only new word will be linked, skipping this that was previously linked
86
87
        Args:
88
          word: str: name of BaseWord on vernacular language
89
          language: str: BaseWord's language (Default value = None)
90
91
        Returns:
92
          Linked BaseKey object or None if it were already linked
93
94
        """
95 1
        language = language if language else self.language
96 1
        result = self.link_keys_from_list_of_str(source=[word, ], language=language)
97 1
        return result[0] if result else None
98
99 1
    def link_keys_from_definition_body(
100
            self, language: str = None,
101
            pattern: str = KEY_PATTERN) -> List[BaseKey]:
102
        """Extract and link keys from BaseDefinition's body
103
104
        Args:
105
          language: str: Language of BaseDefinition's keys (Default value = None)
106
          pattern: str: Regex pattern for extracting keys from the BaseDefinition's body
107
            (Default value = KEY_PATTERN)
108
109
        Returns:
110
          List of linked BaseKey objects
111
112
        """
113 1
        language = language if language else self.language
114 1
        keys = re.findall(pattern, self.body)
115 1
        return self.link_keys_from_list_of_str(source=keys, language=language)
116
117 1
    def link_keys(
118
            self, source: Union[List[str], str, None] = None,
119
            language: str = None, pattern: str = KEY_PATTERN) -> Union[BaseKey, List[BaseKey]]:
120
        """Universal method for linking all available types of key sources with BaseDefinition
121
122
        Args:
123
          source: Union[List[str], str, None]:
124
            If no source is provided, keys will be extracted from the BaseDefinition's body
125
            If source is a string or a list of strings, the language of the keys must be specified
126
            TypeError will be raised if the source contains inappropriate data
127
            (Default value = None)
128
          language: str: Language of BaseDefinition's keys (Default value = None)
129
          pattern: str: Regex pattern for extracting keys from the BaseDefinition's body
130
            (Default value = KEY_PATTERN)
131
132
        Returns:
133
          None, BaseKey, or List of BaseKeys
134
135
        """
136
137 1
        def is_list_of_str(src: list) -> bool:
138 1
            checked_items = all(isinstance(item, str) for item in src)
139 1
            return bool(isinstance(src, list) and checked_items)
140
141 1
        language = language if language else self.language
142
143 1
        if not source:
144 1
            return self.link_keys_from_definition_body(language=language, pattern=pattern)
145
146 1
        if isinstance(source, str):
147 1
            return self.link_key_from_str(word=source, language=language)
148
149 1
        if is_list_of_str(source):
150 1
            return self.link_keys_from_list_of_str(source=source, language=language)
151
152 1
        raise TypeError("Source for keys should have a string, or list of strings."
153
                        "You input %s" % type(source))
154
155 1
    @classmethod
156 1
    def by_key(
157
            cls, key: Union[BaseKey, str],
158
            language: str = None,
159
            case_sensitive: bool = False,
160
            partial_results: bool = False,
161
    ) -> BaseQuery:
162
        """Definition.Query filtered by specified key
163
164
        Args:
165
          key: Union[BaseKey, str]:
166
          language: str: Language of key (Default value = None)
167
          case_sensitive: bool:  (Default value = False)
168
          partial_results: bool:  (Default value = False)
169
170
        Returns:
171
          BaseQuery
172
173
        """
174
175 1
        key = BaseKey.word if isinstance(key, BaseKey) else str(key)
176 1
        request = cls.query.join(t_connect_keys, BaseKey).order_by(BaseKey.word)
177
178 1
        if language:
179 1
            request = request.filter(BaseKey.language == language)
180
181 1
        if case_sensitive:
182 1
            if partial_results:
183 1
                return request.filter(BaseKey.word.like(f"{key}%"))
184 1
            return request.filter(BaseKey.word == key)
185
186 1
        if partial_results:
187 1
            return request.filter(BaseKey.word.ilike(f"{key}%"))
188
189
        return request.filter(BaseKey.word.ilike(key))
190