jknife 0.0.1__py2.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.
@@ -0,0 +1,108 @@
1
+ # import packages from default or pip library
2
+ from datetime import date
3
+ from typing_extensions import Annotated, Doc
4
+ from mongoengine import Document, DateField, StringField, EmailField
5
+ from fastapi.exceptions import RequestValidationError
6
+ from pycountry import countries as pycountries
7
+
8
+ # import packages from this framework
9
+
10
+
11
+ # define Class for Common SQLModel
12
+ class AddressMixin(Document):
13
+ meta = {'abstract': True}
14
+
15
+ address: Annotated[str,
16
+ Doc("Address.")] = StringField(null=False)
17
+
18
+
19
+ class BirthdateMixin(Document):
20
+ meta = {'abstract': True}
21
+
22
+ birthdate: Annotated[date,
23
+ Doc("User's Birthdate.")] = DateField(null=False)
24
+
25
+
26
+ class CPNumMixin(Document):
27
+ meta = {'abstract': True}
28
+
29
+ cp_num: Annotated[str,
30
+ Doc("cellphone number.")] = StringField(null=False)
31
+
32
+
33
+ class EmailMixin(Document):
34
+ meta = {'abstract': True}
35
+
36
+ email: Annotated[str,
37
+ Doc("email address. this can replace username for signup and signin.")] = EmailField(null=False, unique=True)
38
+
39
+
40
+ class FirstnameMixin(Document):
41
+ meta = {'abstract': True}
42
+
43
+ firstname: Annotated[str,
44
+ Doc("First name of application user.")] = StringField(null=False)
45
+
46
+
47
+ class LastnameMixin(Document):
48
+ meta = {'abstract': True}
49
+
50
+ lastname: Annotated[str,
51
+ Doc("Last name of application user.")] = StringField(null=False)
52
+
53
+
54
+ class NationMixin(Document):
55
+ meta = {'abstract': True}
56
+
57
+ nation: Annotated[str,
58
+ Doc("the nation that user came from.")] = StringField(null=False)
59
+
60
+
61
+ class NicknameMixin(Document):
62
+ meta = {'abstract': True}
63
+
64
+ nickname: Annotated[str,
65
+ Doc("User's nickname that used in this application.")] = StringField(unique=True)
66
+
67
+
68
+ class PostalCodeMixin(Document):
69
+ meta = {'abstract': True}
70
+
71
+ postal_code: Annotated[str,
72
+ Doc("postal number of address.")] = StringField(null=False)
73
+
74
+
75
+ # define function to control MongoDB document event
76
+ # please follow the procedure below
77
+ # create event handler function with name starting with _.
78
+ # event handler function must have 3 args: sender, document, **kwargs)
79
+ # each db column can get from 'document'
80
+ # import pre_init or pre_save from 'mongoengine.signals' in your model file.
81
+ # pre_init.connect(EVNET_HANDLER_FUNC_NAME, sender=TABLE_CLASS_NAME)
82
+ def _convert_country_to_alpha2(sender, document, **kwargs):
83
+ if document.nation and len(document.nation) != 2:
84
+ try:
85
+ document.nation = pycountries.get(name=document.nation).alpha_2
86
+ except AttributeError:
87
+ raise RequestValidationError(errors={"input": "nation", "msg": "please input official country name."})
88
+
89
+ return None
90
+
91
+ def _convert_country_to_alpha3(sender, document, **kwargs):
92
+ if document.nation and len(document.nation) != 3:
93
+ try:
94
+ document.nation = pycountries.get(name=document.nation).alpha_3
95
+ except AttributeError:
96
+ raise RequestValidationError(errors={"input": "nation", "msg": "please input official country name."})
97
+
98
+ return None
99
+
100
+ def _lower_firstname(sender, document, **kwargs) -> None:
101
+ if document.firstname:
102
+ document.firstname = document.firstname.lower()
103
+ return None
104
+
105
+ def _lower_lastname(sender, document, **kwargs) -> None:
106
+ if document.lastname:
107
+ document.lastname = document.lastname.lower()
108
+ return None
@@ -0,0 +1,58 @@
1
+ # import packages from default or pip library
2
+ from pycountry import languages as pylanguage
3
+ from typing_extensions import Annotated, Doc
4
+ from mongoengine import Document, BooleanField, IntField, StringField
5
+ from fastapi.exceptions import RequestValidationError
6
+
7
+ # import packages from this framework
8
+
9
+
10
+ # define Class for Common SQLModel
11
+ class AllowMaxAccessSettingMixin(Document):
12
+ meta = {'abstract': True}
13
+
14
+ allow_max_access: Annotated[int,
15
+ Doc("user setting for max access during trying sign in.")] = IntField(null=False, default=5, min_value=3, max_value=5)
16
+
17
+
18
+ class LanguageSettingMixin(Document):
19
+ meta = {'abstract': True}
20
+
21
+ language: Annotated[str,
22
+ Doc("language setting for application user.")] = StringField(null=False)
23
+
24
+ _alpha_code_type: Annotated[str,
25
+ Doc("assign 'alpha_2' or 'alpha_3'. default is 'alpha_2'")] = "alpha_2"
26
+
27
+
28
+ class DarkModeSettingMixin(Document):
29
+ meta = {'abstract': True}
30
+
31
+ is_dark_mode: Annotated[bool,
32
+ Doc("light or dark mode for application user.")] = BooleanField(null=False, default=False)
33
+
34
+
35
+ # define function to control MongoDB document event
36
+ # please follow the procedure below
37
+ # create event handler function with name starting with _.
38
+ # event handler function must have 3 args: sender, document, **kwargs)
39
+ # each db column can get from 'document'
40
+ # import pre_init or pre_save from 'mongoengine.signals' in your model file.
41
+ # pre_init.connect(EVNET_HANDLER_FUNC_NAME, sender=TABLE_CLASS_NAME)
42
+ def _convert_language_to_alpha2(sender, document, **kwargs):
43
+ if document.language and len(document.language) != 2:
44
+ try:
45
+ document.language = pylanguage.get(name=document.language).alpha_2
46
+ except AttributeError:
47
+ raise RequestValidationError(errors={"input": "nation", "msg": "please input official country name."})
48
+
49
+ return None
50
+
51
+ def _convert_language_to_alpha3(sender, document, **kwargs):
52
+ if document.language and len(document.language) != 3:
53
+ try:
54
+ document.language = pylanguage.get(name=document.language).alpha_3
55
+ except AttributeError:
56
+ raise RequestValidationError(errors={"input": "nation", "msg": "please input official country name."})
57
+
58
+ return None
@@ -0,0 +1,62 @@
1
+ # import packages from default or pip library
2
+ from datetime import datetime, timedelta, timezone
3
+ from typing_extensions import Annotated, Doc
4
+ from mongoengine import Document, DateTimeField, StringField, pre_init
5
+
6
+ # import packages from this framework
7
+ from settings import AUTHENTICATION
8
+
9
+ # settings
10
+ TOKEN_VALID_TIME: int = AUTHENTICATION.get("token").get("token_valid_time")
11
+
12
+
13
+ # define classes
14
+ class AccessTokenMixin(Document):
15
+ meta = {'abstract': True}
16
+
17
+ access_token: Annotated[str,
18
+ Doc("This is a access token that will be stored in DB or somewhere")] = StringField(primary_key=True,
19
+ null=False,
20
+ unique=True)
21
+
22
+
23
+ class TokenValidDateTime(Document):
24
+ meta = {'abstract': True}
25
+
26
+ issued_dt: Annotated[datetime,
27
+ Doc("datetime when the token issued.")] = DateTimeField(null=False)
28
+ expiration_dt: Annotated[datetime,
29
+ Doc("datetime when the token will be expired.")] = DateTimeField(null=False)
30
+
31
+
32
+ class RefreshTokenMixin(Document):
33
+ meta = {'abstract': True}
34
+
35
+ refresh_token: Annotated[str,
36
+ Doc("This is a refresh token that will be used for issuing access token again.")] = StringField(null=False,
37
+ unique=True)
38
+
39
+
40
+ class JWTTokens(RefreshTokenMixin, AccessTokenMixin):
41
+ meta = {'abstract': True}
42
+
43
+ token_type: Annotated[str,
44
+ Doc("Token type that used in this class.")] = StringField(null=False,
45
+ default="Bearer")
46
+
47
+
48
+ # define function to control MongoDB document event
49
+ # please follow the procedure below
50
+ # create event handler function with name starting with _.
51
+ # event handler function must have 3 args: sender, document, **kwargs)
52
+ # each db column can get from 'document'
53
+ # import pre_init or pre_save from 'mongoengine.signals' in your model file.
54
+ # pre_init.connect(EVNET_HANDLER_FUNC_NAME, sender=TABLE_CLASS_NAME)
55
+ def _fill_token_datetime_field(sender, document, **kwargs) -> None:
56
+ if document.issued_dt is None:
57
+ document.issued_dt = datetime.now(tz=timezone.utc)
58
+
59
+ if document.expiration_dt is None:
60
+ document.expiration_dt = document.issued_dt + timedelta(minutes=TOKEN_VALID_TIME)
61
+
62
+ return None
@@ -0,0 +1,91 @@
1
+ # import packages from default or pip library
2
+ from datetime import datetime, timezone
3
+ from typing_extensions import Annotated, Doc
4
+ from mongoengine import Document, BooleanField, DateTimeField, IntField, StringField
5
+
6
+ # import packages from this framework
7
+ from src.jknife.db.models.rdbms.users import encrypt_password
8
+
9
+
10
+ # define Class for Common SQLModel
11
+ class LastSigninDateTimeMixin(Document):
12
+ meta = {'abstract': True}
13
+
14
+ last_signin_dt: Annotated[datetime,
15
+ Doc("User's last signin datetime.")] = DateTimeField(null=True, default=None)
16
+
17
+ def update_signin_dt(self) -> None:
18
+ self.last_signin_dt = datetime.now(tz=timezone.utc)
19
+ return None
20
+
21
+
22
+ class SigninFailMixin(Document):
23
+ meta = {'abstract': True}
24
+
25
+ is_active: Annotated[bool,
26
+ Doc("show whether the user is activated or not.")] = BooleanField(null=False, default=False)
27
+ signin_fail: Annotated[int,
28
+ Doc("If the user fail to login, this value will be incremented.")] = IntField(null=False, default=0, ge=0, le=5)
29
+
30
+ def init_signin_fail_count(self) -> None:
31
+ self.signin_fail = 0
32
+ return None
33
+
34
+ def activate_user(self) -> None:
35
+ self.is_active = True
36
+ self.init_signin_fail_count()
37
+ return None
38
+
39
+ def deactivate_user(self) -> None:
40
+ self.is_active = False
41
+ return None
42
+
43
+ def add_signin_fail_count(self, limit: int) -> None:
44
+ if self.signin_fail < limit:
45
+ self.signin_fail += 1
46
+
47
+ if self.signin_fail == limit:
48
+ self.deactivate_user()
49
+
50
+ return None
51
+
52
+
53
+ class IsAdminMixin(Document):
54
+ meta = {'abstract': True}
55
+
56
+ is_admin: Annotated[bool,
57
+ Doc("show whether the user is admin or not.")] = BooleanField(null=False, default=False)
58
+
59
+ def grant_admin(self) -> None:
60
+ self.is_admin = True
61
+ return None
62
+
63
+ def revoke_admin(self) -> None:
64
+ self.is_admin = False
65
+ return None
66
+
67
+
68
+ class PasswordMixin(Document):
69
+ meta = {'abstract': True}
70
+
71
+ password: Annotated[str,
72
+ Doc("password for application user. it is recommended to store password after encrypting.")] = StringField(null=False, unique=False, min_length=32)
73
+
74
+ def encrypt_password(self, enc_type:str="sha256"):
75
+ self.password = encrypt_password(password=self.password, enc_type=enc_type)
76
+
77
+
78
+ class UsernameMixin(Document):
79
+ meta = {'abstract': True}
80
+
81
+ username: Annotated[str,
82
+ Doc("username for application user. if you want to replace it to email, refer to 'contact' module.")] = StringField(null=False)
83
+
84
+
85
+ # define function to control MongoDB document event
86
+ # please follow the procedure below
87
+ # create event handler function with name starting with _.
88
+ # event handler function must have 3 args: sender, document, **kwargs)
89
+ # each db column can get from 'document'
90
+ # import pre_init or pre_save from 'mongoengine.signals' in your model file.
91
+ # pre_init.connect(EVNET_HANDLER_FUNC_NAME, sender=TABLE_CLASS_NAME)
@@ -0,0 +1,53 @@
1
+ # import packages from default or pip library
2
+ from datetime import datetime, timezone
3
+ from typing_extensions import (Annotated,
4
+ Doc,
5
+ Optional)
6
+ from uuid import UUID, uuid4
7
+ from sqlmodel import (SQLModel,
8
+ Field)
9
+
10
+ # import packages from this framework
11
+
12
+
13
+ # define Class for Common SQLModel
14
+ class IdMixin(SQLModel):
15
+ id: Annotated[int,
16
+ Doc("Default integer id for each table row.")] = Field(primary_key=True, default=None)
17
+
18
+
19
+ class UUIDMixin(SQLModel):
20
+ id: Annotated[UUID,
21
+ Doc("UUID format id for each table row.")] = Field(primary_key=True, default=None)
22
+
23
+
24
+ class RegisterDateTimeMixin(SQLModel):
25
+ register_dt: Annotated[datetime,
26
+ Doc("Datetime that the row was added at.")] = Field(nullable=False, default=None)
27
+
28
+
29
+ class UpdateDateTimeMixin(SQLModel):
30
+ update_dt: Annotated[Optional[datetime],
31
+ Doc("Datetime that the row was updated at.")] = Field(nullable=True, default=None)
32
+
33
+
34
+ # define function to control SQL event
35
+ # please follow the procedure below
36
+ # use decorator 'event.listens_for()' for event handling function
37
+ # event handler function must have 3 args: mapper, connection and target)
38
+ # in your model file, import listens_for from 'sqlalchemy.event'
39
+ # event.listens_for(target=TARGET_TABLE_CLASS_NAME, identifier=['before_insert', 'before_update',...])
40
+ def _assign_register_datetime(mapper, connection, target) -> None:
41
+ if target.register_dt is None:
42
+ target.register_dt = datetime.now(tz=timezone.utc)
43
+ return None
44
+
45
+ def _assign_update_datetime(mapper, connection, target) -> None:
46
+ if target.update_dt is not None:
47
+ target.update_dt = datetime.now(tz=timezone.utc)
48
+ return None
49
+
50
+ def _assign_uuid(mapper, connection, target) -> None:
51
+ if target.id is None:
52
+ target.id = uuid4()
53
+ return None
@@ -0,0 +1,24 @@
1
+ # import packages from default or pip library
2
+ from typing_extensions import Annotated, Doc
3
+ from sqlmodel import SQLModel, Field
4
+
5
+ # import packages from this framework
6
+
7
+
8
+ # define Class for Common SQLModel
9
+ class AllowIPsMixin(SQLModel):
10
+ allow_ips: Annotated[list[str],
11
+ Doc("Set IPv4 or IPv6 addresses or networks to allow user to access")] = Field(nullable=False,
12
+ default=["127.0.0.1"])
13
+
14
+
15
+ # define function to control SQL event
16
+ # please follow the procedure below
17
+ # use decorator 'event.listens_for()' for event handling function
18
+ # event handler function must have 3 args: mapper, connection and target)
19
+ # in your model file, import listens_for from 'sqlalchemy.event'
20
+ # event.listens_for(target=TARGET_TABLE_CLASS_NAME, identifier=['before_insert', 'before_update',...])
21
+ def _convert_ips_to_str(mapper, connection, target) -> None:
22
+ if target.allow_ips is not None:
23
+ target.allow_ips = [ str(ip) for ip in target.allow_ips ]
24
+ return None
@@ -0,0 +1,88 @@
1
+ # import packages from default or pip library
2
+ from datetime import date
3
+ from typing_extensions import Annotated, Doc
4
+ from sqlmodel import SQLModel, Field
5
+ from fastapi.exceptions import RequestValidationError
6
+ from pycountry import countries as pycountries
7
+
8
+ # import packages from this framework
9
+
10
+
11
+ # define Class for Common SQLModel
12
+ class AddressMixin(SQLModel):
13
+ address: Annotated[str,
14
+ Doc("Address.")] = Field(nullable=False)
15
+
16
+
17
+ class BirthdateMixin(SQLModel):
18
+ birthdate: Annotated[date,
19
+ Doc("User's Birthdate.")] = Field(nullable=False)
20
+
21
+
22
+ class CPNumMixin(SQLModel):
23
+ cp_num: Annotated[str,
24
+ Doc("cellphone number.")] = Field(nullable=False)
25
+
26
+
27
+ class EmailMixin(SQLModel):
28
+ email: Annotated[str,
29
+ Doc("email address. this can replace username for signup and signin.")] = Field(nullable=False, unique=True)
30
+
31
+
32
+ class FirstnameMixin(SQLModel):
33
+ firstname: Annotated[str,
34
+ Doc("First name of application user.")] = Field(nullable=False, index=True)
35
+
36
+
37
+ class LastnameMixin(SQLModel):
38
+ lastname: Annotated[str,
39
+ Doc("Last name of application user.")] = Field(nullable=False, index=True)
40
+
41
+
42
+ class NationMixin(SQLModel):
43
+ nation: Annotated[str,
44
+ Doc("the nation that user came from.")] = Field(nullable=False)
45
+
46
+
47
+ class NicknameMixin(SQLModel):
48
+ nickname: Annotated[str,
49
+ Doc("User's nickname that used in this application.")] = Field(unique=True, index=True)
50
+
51
+
52
+ class PostalCodeMixin(SQLModel):
53
+ postal_code: Annotated[str,
54
+ Doc("postal number of address.")] = Field(nullable=False)
55
+
56
+
57
+ # define function to control SQL event
58
+ # please follow the procedure below
59
+ # use decorator 'event.listens_for()' for event handling function
60
+ # event handler function must have 3 args: mapper, connection and target)
61
+ # in your model file, import listens_for from 'sqlalchemy.event'
62
+ # event.listens_for(target=TARGET_TABLE_CLASS_NAME, identifier=['before_insert', 'before_update',...])
63
+ def _convert_country_to_alpha2(mapper, connection, target):
64
+ if target.nation is not None and len(target.nation) != 2:
65
+ try:
66
+ target.nation = pycountries.get(name=target.nation).alpha_2
67
+ except AttributeError:
68
+ raise RequestValidationError(errors={"input": "nation", "msg": "please input official country name."})
69
+ return None
70
+
71
+ def _convert_country_to_alpha3(mapper, connection, target):
72
+ if target.nation is not None and len(target.nation) != 3:
73
+ try:
74
+ target.nation = pycountries.get(name=target.nation).alpha_3
75
+ except AttributeError:
76
+ raise RequestValidationError(errors={"input": "nation", "msg": "please input official country name."})
77
+ return None
78
+
79
+ def _lower_firstname(mapper, connection, target) -> None:
80
+ if target.firstname is not None:
81
+ target.firstname = target.firstname.lower()
82
+ return None
83
+
84
+ def _lower_lastname(mapper, connection, target) -> None:
85
+ if target.lastname is not None:
86
+ target.lastname = target.lastname.lower()
87
+ return None
88
+
@@ -0,0 +1,39 @@
1
+ # import packages from default or pip library
2
+ from pycountry import languages as pylanguage
3
+ from typing_extensions import Annotated, Doc
4
+ from sqlmodel import SQLModel, Field
5
+
6
+ # import packages from this framework
7
+
8
+
9
+ # define Class for Common SQLModel
10
+ class AllowMaxAccessSettingMixin(SQLModel):
11
+ allow_max_access: Annotated[int,
12
+ Doc("user setting for max access during trying sign in.")] = Field(nullable=False, default=5, ge=3, le=5)
13
+
14
+
15
+ class LanguageSettingMixin(SQLModel):
16
+ language: Annotated[str,
17
+ Doc("language setting for application user.")] = Field(nullable=False)
18
+
19
+
20
+ class DarkModeSettingMixin(SQLModel):
21
+ is_dark_mode: Annotated[bool,
22
+ Doc("light or dark mode for application user.")] = Field(nullable=False, default=False)
23
+
24
+
25
+ # define function to control SQL event
26
+ # please follow the procedure below
27
+ # use decorator 'event.listens_for()' for event handling function
28
+ # event handler function must have 3 args: mapper, connection and target)
29
+ # in your model file, import listens_for from 'sqlalchemy.event'
30
+ # event.listens_for(target=TARGET_TABLE_CLASS_NAME, identifier=['before_insert', 'before_update',...])
31
+ def _convert_language_to_alpha2(mapper, connection, target) -> None:
32
+ if target.language is not None:
33
+ target.language = pylanguage.get(name=target.language).alpha_2
34
+ return None
35
+
36
+ def _convert_language_to_alpha3(mapper, connection, target) -> None:
37
+ if target.language is not None:
38
+ target.language = pylanguage.get(name=target.language).alpha_3
39
+ return None
@@ -0,0 +1,53 @@
1
+ # import packages from default or pip library
2
+ from datetime import datetime, timedelta, timezone
3
+ from typing_extensions import Annotated, Doc
4
+ from sqlmodel import SQLModel, Field
5
+
6
+ # import packages from this framework
7
+ from settings import AUTHENTICATION
8
+
9
+ # settings
10
+ TOKEN_VALID_TIME: int = AUTHENTICATION.get("token").get("token_valid_time")
11
+
12
+
13
+ # define classes
14
+ class AccessTokenMixin(SQLModel):
15
+ access_token: Annotated[str,
16
+ Doc("This is a access token that will be stored in DB or somewhere")] = Field(primary_key=True,
17
+ nullable=False,
18
+ unique=True)
19
+
20
+
21
+ class RefreshTokenMixin(SQLModel):
22
+ refresh_token: Annotated[str,
23
+ Doc("This is a refresh token that will be used for issuing access token again.")] = Field(nullable=False,
24
+ unique=True)
25
+
26
+
27
+ class TokenValidDateTimeMixin(SQLModel):
28
+ issued_dt: Annotated[datetime,
29
+ Doc("datetime when the token issued.")] = Field(nullable=False)
30
+ expiration_dt: Annotated[datetime,
31
+ Doc("datetime when the token will be expired.")] = Field(nullable=False)
32
+
33
+
34
+ class JWTTokens(RefreshTokenMixin, AccessTokenMixin):
35
+ token_type: Annotated[str,
36
+ Doc("Token type that used in this class.")] = Field(nullable=False,
37
+ default="Bearer")
38
+
39
+
40
+ # define function to control SQL event
41
+ # please follow the procedure below
42
+ # use decorator 'event.listens_for()' for event handling function
43
+ # event handler function must have 3 args: mapper, connection and target)
44
+ # in your model file, import listens_for from 'sqlalchemy.event'
45
+ # event.listens_for(target=TARGET_TABLE_CLASS_NAME, identifier=['before_insert', 'before_update',...])
46
+ def _fill_token_datetime_field(mapper, connection, target) -> None:
47
+ if target.issued_dt is None:
48
+ target.issued_dt = datetime.now(tz=timezone.utc)
49
+
50
+ if target.expired is None:
51
+ target.expired_dt = target.issued_dt + timedelta(minutes=TOKEN_VALID_TIME)
52
+
53
+ return None
@@ -0,0 +1,94 @@
1
+ # import packages from default or pip library
2
+ import hashlib
3
+ from datetime import datetime, timezone
4
+ from typing_extensions import Annotated, Doc
5
+ from sqlmodel import SQLModel, Field
6
+
7
+ # import packages from this framework
8
+ from settings import PASSWORD_POLICIES
9
+
10
+
11
+ # password encryption algorithm
12
+ ENCRYPT_TYPE: str = PASSWORD_POLICIES.get("encrypt_type")
13
+
14
+
15
+ # define Class for Common SQLModel
16
+ class LastSigninDateTimeMixin(SQLModel):
17
+ last_signin_dt: Annotated[datetime,
18
+ Doc("User's last signin datetime.")] = Field(nullable=True, default=None)
19
+
20
+ def update_signin_dt(self) -> None:
21
+ self.last_signin_dt = datetime.now(tz=timezone.utc)
22
+ return None
23
+
24
+
25
+ class SigninFailMixin(SQLModel):
26
+ is_active: Annotated[bool,
27
+ Doc("show whether the user is activated or not.")] = Field(nullable=False, default=False)
28
+ signin_fail: Annotated[int,
29
+ Doc("If the user fail to login, this value will be incremented.")] = Field(nullable=False, default=0, ge=0, le=5)
30
+
31
+ def activate_user(self) -> None:
32
+ self.is_active = True
33
+ self.__init_signin_fail_count()
34
+ return None
35
+
36
+ def deactivate_user(self) -> None:
37
+ self.is_active = False
38
+ return None
39
+
40
+ def add_signin_fail_count(self, limit: int) -> None:
41
+ if self.signin_fail < limit:
42
+ self.signin_fail += 1
43
+
44
+ if self.signin_fail == limit:
45
+ self.deactivate_user()
46
+
47
+ return None
48
+
49
+ def __init_signin_fail_count(self) -> None:
50
+ self.signin_fail = 0
51
+ return None
52
+
53
+
54
+ class IsAdminMixin(SQLModel):
55
+ is_admin: Annotated[bool,
56
+ Doc("show whether the user is admin or not.")] = Field(nullable=False, default=False)
57
+
58
+ def grant_admin(self) -> None:
59
+ self.is_admin = True
60
+ return None
61
+
62
+ def revoke_admin(self) -> None:
63
+ self.is_admin = False
64
+ return None
65
+
66
+
67
+ class PasswordMixin(SQLModel):
68
+ password: Annotated[str,
69
+ Doc("password for application user. it is recommended to store password after encrypting.")] = Field(nullable=False, unique=False, min_length=32)
70
+
71
+ def encrypt_password(self, enc_type:str=ENCRYPT_TYPE):
72
+ self.password = encrypt_password(password=self.password, enc_type=enc_type)
73
+
74
+
75
+ class UsernameMixin(SQLModel):
76
+ username: Annotated[str,
77
+ Doc("username for application user. if you want to replace it to email, refer to 'contact' module.")] = Field(nullable=False, unique=True, min_length=8)
78
+
79
+
80
+ # define function for users
81
+ def encrypt_password(password:str, enc_type:str=ENCRYPT_TYPE) -> str:
82
+ if enc_type not in hashlib.algorithms_available:
83
+ raise ValueError(f"'{enc_type}' is not supported hash method in this application. Password will be encrypted with default hash.")
84
+
85
+ return getattr(hashlib, enc_type)(string=password.encode("utf-8")).hexdigest()
86
+
87
+
88
+ # define function to control SQL event
89
+ # please follow the procedure below
90
+ # use decorator 'event.listens_for()' for event handling function
91
+ # event handler function must have 3 args: mapper, connection and target)
92
+ # in your model file, import listens_for from 'sqlalchemy.event'
93
+ # event.listens_for(target=TARGET_TABLE_CLASS_NAME, identifier=['before_insert', 'before_update',...])
94
+
File without changes