ecodev-core 0.0.58__py3-none-any.whl → 0.0.59__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of ecodev-core might be problematic. Click here for more details.

ecodev_core/__init__.py CHANGED
@@ -37,11 +37,17 @@ from ecodev_core.db_connection import engine
37
37
  from ecodev_core.db_connection import get_session
38
38
  from ecodev_core.db_connection import info_message
39
39
  from ecodev_core.db_filters import ServerSideFilter
40
+ from ecodev_core.db_i18n import I18nMixin
41
+ from ecodev_core.db_i18n import Lang
42
+ from ecodev_core.db_i18n import get_lang
43
+ from ecodev_core.db_i18n import localized_col
44
+ from ecodev_core.db_i18n import set_lang
40
45
  from ecodev_core.db_insertion import generic_insertion
41
46
  from ecodev_core.db_insertion import get_raw_df
42
47
  from ecodev_core.db_retrieval import count_rows
43
48
  from ecodev_core.db_retrieval import get_rows
44
49
  from ecodev_core.db_retrieval import ServerSideField
50
+ from ecodev_core.db_upsertion import add_missing_columns
45
51
  from ecodev_core.db_upsertion import add_missing_enum_values
46
52
  from ecodev_core.db_upsertion import field
47
53
  from ecodev_core.db_upsertion import filter_to_sfield_dict
@@ -111,4 +117,5 @@ __all__ = [
111
117
  'sort_by_keys', 'sort_by_values', 'Settings', 'load_yaml_file', 'Deployment', 'Version',
112
118
  'sfield', 'field', 'upsert_df_data', 'upsert_deletor', 'get_row_versions', 'get_versions',
113
119
  'db_to_value', 'upsert_data', 'upsert_selector', 'get_sfield_columns', 'filter_to_sfield_dict',
114
- 'SETTINGS', 'add_missing_enum_values', 'ban_token', 'TokenBanlist', 'is_banned']
120
+ 'SETTINGS', 'add_missing_enum_values', 'ban_token', 'TokenBanlist', 'is_banned',
121
+ 'get_lang', 'set_lang', 'Lang', 'localized_col', 'I18nMixin', 'add_missing_columns']
ecodev_core/db_i18n.py ADDED
@@ -0,0 +1,213 @@
1
+ """
2
+ Module implementing internationalization (i18n) for sqlmodel
3
+ """
4
+
5
+ import contextvars
6
+ from enum import Enum
7
+ from typing import Optional
8
+
9
+ from sqlalchemy import Label
10
+ from sqlalchemy import label
11
+ from sqlmodel import func
12
+ from sqlmodel.main import SQLModelMetaclass
13
+
14
+ from ecodev_core.list_utils import first_func_or_default
15
+
16
+ class Lang(str, Enum):
17
+ """
18
+ Enum of the languages available for localization.
19
+ """
20
+ EN = 'en'
21
+ FR = 'fr'
22
+
23
+
24
+ DB_Lang = 'db_Lang'
25
+ CONTEXT_DB_Lang = contextvars.ContextVar(DB_Lang, default=Lang.EN)
26
+ """Context variables for storing the active database language, defaults to Lang.EN"""
27
+
28
+
29
+ def set_lang(lang: Lang) -> None:
30
+ """
31
+ Sets the `CONTEXT_DB_Lang` context var.
32
+
33
+ Args:
34
+ lang (Lang): The language to assign to the `CONTEXT_DB_Lang` context var.
35
+ """
36
+ CONTEXT_DB_Lang.set(lang)
37
+
38
+
39
+ def get_lang() -> Lang:
40
+ """
41
+ Fetches the value of `CONTEXT_DB_Lang` context var.
42
+
43
+ Returns:
44
+ lang (Lang): The value of `CONTEXT_DB_Lang` context var
45
+ """
46
+ return Lang(CONTEXT_DB_Lang.get())
47
+
48
+
49
+ class I18nMixin:
50
+ """
51
+ I18n (localization) mixin for string attributes of pydantic BaseModel classes.
52
+
53
+ Maps arbitrary string attributes of the class to their localized values. Localized fields
54
+ should be defined following the rules below :
55
+ - The field name must be defined as a key of the private attribute `__localized_fields__`
56
+ - Each field defined in `__localized_fields__` must be present as an attribute for \
57
+ each of its localized versions in the following format <field>_<lang>.
58
+ For example :
59
+ ```
60
+ __localized_fields__ = {
61
+ 'name':[Lang.EN, Lang.FR]
62
+ }
63
+ ```
64
+ assumes that the attributes `name_en` and `name_fr` are attribute of the class.
65
+ These attributes must have a type `str`.
66
+ - All localized field must have a localized version using `__fallback_lang__`
67
+
68
+ Args:
69
+ __localized_fields__ (dict[str, list[Lang]]): Mapping between localized fields and a \
70
+ list of their available localized versions. Defaults to {}.
71
+ __fallback_lang__ (Lang): Fallback locale if the requested localized version of a \
72
+ field is None. Defaults to Lang.EN.
73
+ """
74
+ __localized_fields__: dict[str, list[Lang]] = {}
75
+ __fallback_lang__: Lang = Lang.EN
76
+
77
+ @classmethod
78
+ def _get_lang_chain(cls, field: str, lang: Optional[Lang] = None) -> list[Lang]:
79
+ """
80
+ Returns the chain of localized versions of the requested field with the priority given
81
+ to the `lang` argument, followed by the lang returned by
82
+ [get_lang][ecodev_core.db_i18n.get_lang] and finally the lang defined in
83
+ `cls.__fallback_lang__`.
84
+
85
+ Args:
86
+ field (str): Name of the attribute/field to localize
87
+ lang (Optional[Lang]): The requested locale language. If none, then uses that
88
+ returned by [get_lang][ecodev_core.db_i18n.get_lang]. Defaults to None.
89
+
90
+ Returns:
91
+ list[Lang]: List of Lang enums to use for generating the name of the localized \
92
+ fields.
93
+ """
94
+ if field not in cls.__localized_fields__:
95
+ raise AttributeError(f'Field {field!r} is not internationalized.')
96
+
97
+ available_langs = cls.__localized_fields__[field]
98
+
99
+ if cls.__fallback_lang__ not in available_langs:
100
+ raise AttributeError(
101
+ f'Fallback language {cls.__fallback_lang__!r} not available for field {field!r}. '
102
+ f'Available: {available_langs}'
103
+ )
104
+
105
+ lang = lang or get_lang()
106
+ if lang not in cls.__localized_fields__[field]:
107
+ raise AttributeError(f'Field {field!r} is not localized to {lang!r}')
108
+
109
+ return list(set([lang, cls.__fallback_lang__]))
110
+
111
+ @classmethod
112
+ def _get_localized_field_name(cls, field: str, lang: Lang) -> str:
113
+ """
114
+ Returns the name of the localized version of `field` for the requested `lang` in the
115
+ following format <field>_<lang>.
116
+
117
+ Args:
118
+ field (str): Name of the attribute/field to localize
119
+ lang (Optional[Lang]): The requested locale language.
120
+ Returns:
121
+ str: the name of the localized version of `field` for the requested `lang`
122
+ """
123
+ return f'{field}_{lang.value}'
124
+
125
+ @classmethod
126
+ def get_localized_field_chain(cls, field: str, lang: Optional[Lang] = None) -> list[str]:
127
+ """
128
+ Returns a chain of the localized versions of the requested field with the priority given
129
+ to the `lang` argument, followed by the lang returned by
130
+ [get_lang][ecodev_core.db_i18n.get_lang] and finally
131
+ the lang defined in `cls.__fallback_lang__`
132
+
133
+ Args:
134
+ field (str): Name of the attribute/field to localize
135
+ lang (Optional[Lang]): The requested locale language. If none, then uses that
136
+ returned by [get_lang][ecodev_core.db_i18n.get_lang]. Defaults to None.
137
+
138
+ Returns:
139
+ list[str]: chain of the localized versions of the requested field.
140
+
141
+ """
142
+ return [cls._get_localized_field_name(field, lang)
143
+ for lang in cls._get_lang_chain(field, lang)]
144
+
145
+ def _get_localized(self, field: str, lang: Optional[Lang] = None) -> Optional[str]:
146
+ """
147
+ Returns the localized version of a field.
148
+
149
+ The localized version is returned following the rules defined below :
150
+ - If the requested localized version is not available then the an attempt \
151
+ will be made to localize the field using `__fallback_lang__`
152
+ - The specified language can be passed to `_get_localized`. If it is not passed, \
153
+ the value returned by [get_lang][ecodev_core.db_i18n.get_lang]
154
+ is used instead (Defaults to Lang.EN)
155
+ - If None is returned using the format <field>_<lang> for the language defined in \
156
+ `__localized_fields__` & `__fallback_lang__` is found then returns None
157
+
158
+ Args:
159
+ field (str): Name of the attribute/field to localize
160
+ lang (Optional[Lang]): Requested locale. If None, then fetched from \
161
+ [get_lang][ecodev_core.db_i18n.get_lang]. Defaults to None.
162
+
163
+ Return:
164
+ localized_field (Optional[str]): localized version of the field
165
+ """
166
+ lang_chain = self._get_lang_chain(field=field, lang=lang)
167
+
168
+ for lang in lang_chain:
169
+ attr = self._get_localized_field_name(field=field, lang=lang)
170
+ value = getattr(self, attr, None)
171
+ if value:
172
+ return value
173
+
174
+ return None
175
+
176
+ def __getattr__(self, item: str) -> Optional[str]:
177
+ """
178
+ Overrides __getattr__ to get the localized value of a item if it figures in
179
+ `__localized_fields__`.
180
+ """
181
+ if item in self.__localized_fields__:
182
+ return self._get_localized(item)
183
+ raise AttributeError(f'{self.__class__.__name__!r} object has no attribute {item!r}')
184
+
185
+
186
+ def localized_col(
187
+ field: str,
188
+ db_schema: SQLModelMetaclass,
189
+ lang: Optional[Lang] = None,
190
+ ) -> Label:
191
+ """
192
+ Returns the localized version of `field` for the requested `lang` of a
193
+ given SqlModel class. If `lang` is not specified, then fetches the active
194
+ locale from [get_lang][ecodev_core.db_i18n.get_lang].
195
+
196
+ Args:
197
+ field (str): Name of the field to localize
198
+ db_schema (SQLModelMetaclass): SQLModelMetaclass instance from which the localized \
199
+ fields will be fetched
200
+ lang (Optional[Lang]): Requested locale language. If None, then fetches language \
201
+ from [get_lang][ecodev_core.db_i18n.get_lang] Defaults to None.
202
+
203
+ Returns:
204
+ localized_field (Label): Localized version of the requested `field` wrapped in label \
205
+ with the name of `field`.
206
+ """
207
+ if not issubclass(db_schema, I18nMixin):
208
+ raise TypeError(f"{db_schema.__name__} does not inherit from I18nMixin")
209
+
210
+ localized_fields_chain = db_schema.get_localized_field_chain(field, lang)
211
+ coalesce_fields = [getattr(db_schema, field_name) for field_name in localized_fields_chain]
212
+
213
+ return label(field, func.coalesce(*coalesce_fields))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ecodev-core
3
- Version: 0.0.58
3
+ Version: 0.0.59
4
4
  Summary: Low level sqlmodel/fastapi/pydantic building blocks
5
5
  License: MIT
6
6
  Author: Thomas Epelbaum
@@ -1,4 +1,4 @@
1
- ecodev_core/__init__.py,sha256=K3mOMR75omZcvKBb_ki2upUZ2VjntJFXUoGZdOzUHJQ,6280
1
+ ecodev_core/__init__.py,sha256=nSJL_YUufYyP6GmCccuG1bOAMigY-lkt-dISjT3O7u8,6633
2
2
  ecodev_core/app_activity.py,sha256=KBtI-35LBLPDppFB7xjxWthXQrY3Z_aGDnC-HrW8Ea0,4641
3
3
  ecodev_core/app_rights.py,sha256=RZPdDtydFqc_nFj96huKAc56BS0qS6ScKv4Kghqd6lc,726
4
4
  ecodev_core/app_user.py,sha256=r1bqA4H08x53XmxmjwyGKl_PFjYQazzBbVErdkztqeE,2947
@@ -9,6 +9,7 @@ ecodev_core/check_dependencies.py,sha256=aFn8GI4eBbuJT8RxsfhSSnlpNYYj_LPOH-tZF0E
9
9
  ecodev_core/custom_equal.py,sha256=2gRn0qpyJ8-Kw9GQSueu0nLngLrRrwyMPlP6zqPac0U,899
10
10
  ecodev_core/db_connection.py,sha256=scur99ohLM4Mt931-1RAQh8b6YqFyyMMFkzz8eCxdik,2589
11
11
  ecodev_core/db_filters.py,sha256=T_5JVF27UEu7sC6NOm7-W3_Y0GLfbWQO_EeTXcD2cv8,5041
12
+ ecodev_core/db_i18n.py,sha256=1yqNtS9Uc8jV24dP_o9dCj8BJXlB5SPID5cqWCvUJjE,8329
12
13
  ecodev_core/db_insertion.py,sha256=k-r798MMrW1sRb-gb8lQTxyJrb4QP5iZT8GDzCYYwlo,4544
13
14
  ecodev_core/db_retrieval.py,sha256=sCP7TDGIcTOK5gT3Inga91bE4S31HbQPw4yI22WJbss,7392
14
15
  ecodev_core/db_upsertion.py,sha256=ttngwZzJUQdr9Mc-qkDljJpru5yN3kvAzAMTW84CLHo,13136
@@ -27,7 +28,7 @@ ecodev_core/settings.py,sha256=UvaTv8S_HvfFAL-m1Rfqv_geSGcccuV3ziR1o1d5wu4,1795
27
28
  ecodev_core/sqlmodel_utils.py,sha256=t57H3QPtKRy4ujic1clMK_2L4p0yjGJLZbDjHPZ8M94,453
28
29
  ecodev_core/token_banlist.py,sha256=rKXG9QkfCpjOTr8gBgdX-KYNHAkKvQ9TRnGS99VC9Co,491
29
30
  ecodev_core/version.py,sha256=eyIf8KkW_t-hMuYFIoy0cUlNaMewLe6i45m2HKZKh0Q,4403
30
- ecodev_core-0.0.58.dist-info/LICENSE.md,sha256=8dqVJEbwXjPWjjRKjdLMym5k9Gi8hwtrHh84sti6KIs,1068
31
- ecodev_core-0.0.58.dist-info/METADATA,sha256=JcMRV40hf2VBIgMlP1-pA8fu6cJHYZswnY9_1Xo-CJk,3510
32
- ecodev_core-0.0.58.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
33
- ecodev_core-0.0.58.dist-info/RECORD,,
31
+ ecodev_core-0.0.59.dist-info/LICENSE.md,sha256=8dqVJEbwXjPWjjRKjdLMym5k9Gi8hwtrHh84sti6KIs,1068
32
+ ecodev_core-0.0.59.dist-info/METADATA,sha256=QtE82GdAR5wZihJtAsNt68qd4c3ftl88uc3cZ3IhLko,3510
33
+ ecodev_core-0.0.59.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
34
+ ecodev_core-0.0.59.dist-info/RECORD,,