ecodev-core 0.0.16__tar.gz

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.

@@ -0,0 +1,11 @@
1
+ Copyright 2024 Thomas EPELBAUM for EcoAct
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction,
4
+ including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
5
+ subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
10
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
11
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,79 @@
1
+ Metadata-Version: 2.1
2
+ Name: ecodev-core
3
+ Version: 0.0.16
4
+ Summary: Low level sqlmodel/fastapi/pydantic building blocks
5
+ License: MIT
6
+ Author: Thomas Epelbaum
7
+ Author-email: tomepel@gmail.com
8
+ Requires-Python: >=3.11,<4.0
9
+ Classifier: Development Status :: 2 - Pre-Alpha
10
+ Classifier: Environment :: Web Environment
11
+ Classifier: Framework :: AsyncIO
12
+ Classifier: Framework :: FastAPI
13
+ Classifier: Framework :: Pydantic
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Intended Audience :: Information Technology
16
+ Classifier: Intended Audience :: Science/Research
17
+ Classifier: Intended Audience :: System Administrators
18
+ Classifier: License :: OSI Approved :: MIT License
19
+ Classifier: Operating System :: OS Independent
20
+ Classifier: Programming Language :: Python
21
+ Classifier: Programming Language :: Python :: 3
22
+ Classifier: Programming Language :: Python :: 3.11
23
+ Classifier: Programming Language :: Python :: 3.12
24
+ Classifier: Programming Language :: Python :: 3 :: Only
25
+ Classifier: Programming Language :: Python :: 3.10
26
+ Classifier: Topic :: Database
27
+ Classifier: Topic :: Database :: Database Engines/Servers
28
+ Classifier: Topic :: Internet
29
+ Classifier: Topic :: Internet :: WWW/HTTP
30
+ Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
31
+ Classifier: Topic :: Software Development
32
+ Classifier: Topic :: Software Development :: Libraries
33
+ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
34
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
35
+ Classifier: Typing :: Typed
36
+ Requires-Dist: fastapi (>=0,<1)
37
+ Requires-Dist: httpx (>=0,<1)
38
+ Requires-Dist: numpy (>=1,<2)
39
+ Requires-Dist: openpyxl (>=3,<4)
40
+ Requires-Dist: pandas (>=2,<3)
41
+ Requires-Dist: passlib[bcyrypt] (>=1,<2)
42
+ Requires-Dist: psycopg2-binary (>=2,<3)
43
+ Requires-Dist: pydantic-settings (>=2,<3)
44
+ Requires-Dist: pydantic[python-dotenv] (>=2,<3)
45
+ Requires-Dist: python-jose[cryptography] (>=3,<4)
46
+ Requires-Dist: sqladmin (==0.15.2)
47
+ Requires-Dist: sqlmodel (>=0,<1)
48
+ Description-Content-Type: text/markdown
49
+
50
+ # ecodev-core
51
+
52
+ <p align="center">
53
+ <a href="https://github.com/FR-PAR-ECOACT/ecodev-core/actions" target="_blank">
54
+ <img src="https://github.com/FR-PAR-ECOACT/ecodev-core/blob/main/badges/coverage.svg" alt="Coverage">
55
+ </a>
56
+ <a href="https://github.com/FR-PAR-ECOACT/ecodev-core/actions" target="_blank">
57
+ <img src="https://github.com/FR-PAR-ECOACT/ecodev-core/blob/main/badges/pylint.svg" alt="Publish">
58
+ </a>
59
+ <a href="https://github.com/FR-PAR-ECOACT/ecodev-core/actions/workflows/code-quality.yml/badge.svg" target="_blank">
60
+ <img src="https://github.com/FR-PAR-ECOACT/ecodev-core/actions/workflows/code-quality.yml/badge.svg" alt="Package version">
61
+ </a>
62
+ </p>
63
+
64
+ Low level ecoact generic code. Aimed at being published in open source with poetry
65
+
66
+ ## Installation of this package
67
+
68
+ You are strongly encouraged to install this package via Docker.
69
+
70
+ Starting from a project with a Docker file:
71
+ * add the module ecodev-core in the `requirements.txt` file
72
+ * make sure the `.env` file includes all required fields (see `BaseSettings` and `AuthenticationConfiguration`)
73
+ * build the new version of the Docker container (typically `docker build --tag xxx .`)
74
+ * run it with docker compose (`dc up -d`).
75
+
76
+ ## Documentation
77
+
78
+ Please find it in the [associated mkdoc website!](https://ecodev-doc.lcabox.com/)
79
+
@@ -0,0 +1,29 @@
1
+ # ecodev-core
2
+
3
+ <p align="center">
4
+ <a href="https://github.com/FR-PAR-ECOACT/ecodev-core/actions" target="_blank">
5
+ <img src="https://github.com/FR-PAR-ECOACT/ecodev-core/blob/main/badges/coverage.svg" alt="Coverage">
6
+ </a>
7
+ <a href="https://github.com/FR-PAR-ECOACT/ecodev-core/actions" target="_blank">
8
+ <img src="https://github.com/FR-PAR-ECOACT/ecodev-core/blob/main/badges/pylint.svg" alt="Publish">
9
+ </a>
10
+ <a href="https://github.com/FR-PAR-ECOACT/ecodev-core/actions/workflows/code-quality.yml/badge.svg" target="_blank">
11
+ <img src="https://github.com/FR-PAR-ECOACT/ecodev-core/actions/workflows/code-quality.yml/badge.svg" alt="Package version">
12
+ </a>
13
+ </p>
14
+
15
+ Low level ecoact generic code. Aimed at being published in open source with poetry
16
+
17
+ ## Installation of this package
18
+
19
+ You are strongly encouraged to install this package via Docker.
20
+
21
+ Starting from a project with a Docker file:
22
+ * add the module ecodev-core in the `requirements.txt` file
23
+ * make sure the `.env` file includes all required fields (see `BaseSettings` and `AuthenticationConfiguration`)
24
+ * build the new version of the Docker container (typically `docker build --tag xxx .`)
25
+ * run it with docker compose (`dc up -d`).
26
+
27
+ ## Documentation
28
+
29
+ Please find it in the [associated mkdoc website!](https://ecodev-doc.lcabox.com/)
@@ -0,0 +1,81 @@
1
+ """
2
+ Module listing all public method from the ecodev_core modules
3
+ """
4
+ from ecodev_core.app_activity import AppActivity
5
+ from ecodev_core.app_activity import dash_monitor
6
+ from ecodev_core.app_activity import fastapi_monitor
7
+ from ecodev_core.app_activity import get_method
8
+ from ecodev_core.app_activity import get_recent_activities
9
+ from ecodev_core.app_rights import AppRight
10
+ from ecodev_core.app_user import AppUser
11
+ from ecodev_core.app_user import select_user
12
+ from ecodev_core.app_user import upsert_app_users
13
+ from ecodev_core.auth_configuration import AUTH
14
+ from ecodev_core.authentication import attempt_to_log
15
+ from ecodev_core.authentication import get_access_token
16
+ from ecodev_core.authentication import get_app_services
17
+ from ecodev_core.authentication import get_current_user
18
+ from ecodev_core.authentication import get_user
19
+ from ecodev_core.authentication import is_admin_user
20
+ from ecodev_core.authentication import is_authorized_user
21
+ from ecodev_core.authentication import is_monitoring_user
22
+ from ecodev_core.authentication import JwtAuth
23
+ from ecodev_core.authentication import safe_get_user
24
+ from ecodev_core.authentication import SCHEME
25
+ from ecodev_core.authentication import Token
26
+ from ecodev_core.backup import backup
27
+ from ecodev_core.check_dependencies import check_dependencies
28
+ from ecodev_core.check_dependencies import compute_dependencies
29
+ from ecodev_core.custom_equal import custom_equal
30
+ from ecodev_core.db_connection import create_db_and_tables
31
+ from ecodev_core.db_connection import DB_URL
32
+ from ecodev_core.db_connection import delete_table
33
+ from ecodev_core.db_connection import engine
34
+ from ecodev_core.db_connection import get_session
35
+ from ecodev_core.db_connection import info_message
36
+ from ecodev_core.db_filters import ServerSideFilter
37
+ from ecodev_core.db_insertion import generic_insertion
38
+ from ecodev_core.db_insertion import get_raw_df
39
+ from ecodev_core.db_retrieval import count_rows
40
+ from ecodev_core.db_retrieval import get_rows
41
+ from ecodev_core.db_retrieval import ServerSideField
42
+ from ecodev_core.enum_utils import enum_converter
43
+ from ecodev_core.list_utils import first_or_default
44
+ from ecodev_core.list_utils import first_transformed_or_default
45
+ from ecodev_core.list_utils import group_by
46
+ from ecodev_core.list_utils import group_by_value
47
+ from ecodev_core.list_utils import lselect
48
+ from ecodev_core.list_utils import lselectfirst
49
+ from ecodev_core.logger import log_critical
50
+ from ecodev_core.logger import logger_get
51
+ from ecodev_core.pandas_utils import get_excelfile
52
+ from ecodev_core.pandas_utils import jsonify_series
53
+ from ecodev_core.pandas_utils import pd_equals
54
+ from ecodev_core.permissions import Permission
55
+ from ecodev_core.pydantic_utils import Basic
56
+ from ecodev_core.pydantic_utils import CustomFrozen
57
+ from ecodev_core.pydantic_utils import Frozen
58
+ from ecodev_core.pydantic_utils import OrmFrozen
59
+ from ecodev_core.read_write import load_json_file
60
+ from ecodev_core.read_write import make_dir
61
+ from ecodev_core.read_write import write_json_file
62
+ from ecodev_core.safe_utils import boolify
63
+ from ecodev_core.safe_utils import floatify
64
+ from ecodev_core.safe_utils import intify
65
+ from ecodev_core.safe_utils import safe_clt
66
+ from ecodev_core.safe_utils import SafeTestCase
67
+ from ecodev_core.safe_utils import SimpleReturn
68
+ from ecodev_core.safe_utils import stringify
69
+
70
+ __all__ = [
71
+ 'AUTH', 'Token', 'get_app_services', 'attempt_to_log', 'get_current_user', 'is_admin_user',
72
+ 'write_json_file', 'load_json_file', 'make_dir', 'check_dependencies', 'compute_dependencies',
73
+ 'engine', 'create_db_and_tables', 'get_session', 'info_message', 'group_by_value', 'OrmFrozen',
74
+ 'first_or_default', 'lselect', 'lselectfirst', 'first_transformed_or_default', 'log_critical',
75
+ 'logger_get', 'Permission', 'AppUser', 'AppRight', 'Basic', 'Frozen', 'CustomFrozen', 'JwtAuth',
76
+ 'SafeTestCase', 'SimpleReturn', 'safe_clt', 'stringify', 'boolify', 'get_user', 'floatify',
77
+ 'delete_table', 'SCHEME', 'DB_URL', 'pd_equals', 'jsonify_series', 'upsert_app_users', 'intify',
78
+ 'enum_converter', 'ServerSideFilter', 'get_rows', 'count_rows', 'ServerSideField', 'get_raw_df',
79
+ 'generic_insertion', 'custom_equal', 'is_authorized_user', 'get_method', 'AppActivity',
80
+ 'fastapi_monitor', 'dash_monitor', 'is_monitoring_user', 'get_recent_activities', 'select_user',
81
+ 'get_access_token', 'safe_get_user', 'backup', 'group_by', 'get_excelfile']
@@ -0,0 +1,108 @@
1
+ """
2
+ Module implementing a simple monitoring table
3
+ """
4
+ import inspect
5
+ from datetime import datetime
6
+ from typing import Dict
7
+ from typing import Optional
8
+
9
+ from sqlmodel import col
10
+ from sqlmodel import Field
11
+ from sqlmodel import select
12
+ from sqlmodel import Session
13
+ from sqlmodel import SQLModel
14
+
15
+ from ecodev_core.app_user import AppUser
16
+ from ecodev_core.authentication import get_user
17
+ from ecodev_core.db_connection import engine
18
+
19
+
20
+ """
21
+ Simple helper to retrieve the method name in which this helper is called
22
+
23
+ NB: this is meant to stay a lambda, otherwise the name retrieved is get_method, not the caller
24
+ """
25
+
26
+
27
+ def get_method(): return inspect.stack()[1][3]
28
+
29
+
30
+ class AppActivityBase(SQLModel):
31
+ """
32
+ Simple monitoring class
33
+
34
+ Attributes are:
35
+ - user: the name of the user that triggered the monitoring log
36
+ - application: the application in which the user triggered the monitoring log
37
+ - method: the method called by the user that triggered the monitoring log
38
+ - relevant_option: if filled, complementary information on method (num of treated lines...)
39
+ """
40
+ user: str = Field(index=True)
41
+ application: str = Field(index=True)
42
+ method: str = Field(index=True)
43
+ relevant_option: Optional[str] = Field(index=True, default=None)
44
+
45
+
46
+ class AppActivity(AppActivityBase, table=True): # type: ignore
47
+ """
48
+ The table version of the AppActivityBase monitoring class
49
+ """
50
+ __tablename__ = 'app_activity'
51
+ id: Optional[int] = Field(default=None, primary_key=True)
52
+ created_at: datetime = Field(default_factory=datetime.utcnow)
53
+
54
+
55
+ def dash_monitor(method: str,
56
+ token: Dict,
57
+ application: str,
58
+ relevant_option: Optional[str] = None):
59
+ """
60
+ Generic dash monitor.
61
+
62
+ Attributes are:
63
+ - method: the method called by the user that triggered the monitoring log
64
+ - token: contains the information on the user that triggered the monitoring log
65
+ - application: the application in which the user triggered the monitoring log
66
+ - relevant_option: if filled, complementary information on method (num of treated lines...)
67
+ """
68
+ with Session(engine) as session:
69
+ user = get_user(token.get('token', {}).get('access_token'))
70
+ add_activity_to_db(method, user, application, session, relevant_option)
71
+
72
+
73
+ def fastapi_monitor(method: str,
74
+ user: AppUser,
75
+ application: str,
76
+ session: Session,
77
+ relevant_option: Optional[str] = None):
78
+ """
79
+ Generic fastapi monitor.
80
+
81
+ Attributes are:
82
+ - method: the method called by the user that triggered the monitoring log
83
+ - user: the name of the user that triggered the monitoring log
84
+ - application: the application in which the user triggered the monitoring log
85
+ - session: db connection
86
+ - relevant_option: if filled, complementary information on method (num of treated lines...)
87
+ """
88
+ add_activity_to_db(method, user, application, session, relevant_option)
89
+
90
+
91
+ def add_activity_to_db(method: str,
92
+ user: AppUser,
93
+ application: str,
94
+ session: Session,
95
+ relevant_option: Optional[str] = None):
96
+ """
97
+ Add a new entry in AppActivity given the passed arguments
98
+ """
99
+ session.add(AppActivity(user=user.user, application=application, method=method,
100
+ relevant_option=relevant_option))
101
+ session.commit()
102
+
103
+
104
+ def get_recent_activities(last_date: str, session: Session):
105
+ """
106
+ Returns all activities that happened after last_date
107
+ """
108
+ return session.exec(select(AppActivity).where(col(AppActivity.created_at) > last_date)).all()
@@ -0,0 +1,24 @@
1
+ """
2
+ Module implementing the sqlmodel orm part of the right table
3
+ """
4
+ from typing import Optional
5
+ from typing import TYPE_CHECKING
6
+
7
+ from sqlmodel import Field
8
+ from sqlmodel import Relationship
9
+ from sqlmodel import SQLModel
10
+
11
+
12
+ if TYPE_CHECKING: # pragma: no cover
13
+ from ecodev_core.app_user import AppUser
14
+
15
+
16
+ class AppRight(SQLModel, table=True): # type: ignore
17
+ """
18
+ Simple right class: listing all app_services that a particular user can access to
19
+ """
20
+ __tablename__ = 'app_right'
21
+ id: Optional[int] = Field(default=None, primary_key=True)
22
+ app_service: str
23
+ user_id: Optional[int] = Field(default=None, foreign_key='app_user.id')
24
+ user: Optional['AppUser'] = Relationship(back_populates='rights')
@@ -0,0 +1,92 @@
1
+ """
2
+ Module implementing the sqlmodel orm part of the user table
3
+ """
4
+ from pathlib import Path
5
+ from typing import Dict
6
+ from typing import List
7
+ from typing import Optional
8
+ from typing import TYPE_CHECKING
9
+
10
+ import pandas as pd
11
+ from sqlmodel import col
12
+ from sqlmodel import Field
13
+ from sqlmodel import Relationship
14
+ from sqlmodel import select
15
+ from sqlmodel import Session
16
+ from sqlmodel import SQLModel
17
+ from sqlmodel.sql.expression import SelectOfScalar
18
+
19
+ from ecodev_core.db_insertion import create_or_update
20
+ from ecodev_core.db_insertion import Insertor
21
+ from ecodev_core.permissions import Permission
22
+ from ecodev_core.read_write import load_json_file
23
+
24
+
25
+ if TYPE_CHECKING: # pragma: no cover
26
+ from ecodev_core.app_rights import AppRight
27
+
28
+
29
+ class AppUser(SQLModel, table=True): # type: ignore
30
+ """
31
+ Simple user class: an id associate to a user with a password
32
+ """
33
+ __tablename__ = 'app_user'
34
+ id: Optional[int] = Field(default=None, primary_key=True)
35
+ user: str = Field(index=True)
36
+ password: str
37
+ permission: Permission = Field(default=Permission.ADMIN)
38
+ client: Optional[str] = Field(default=None)
39
+ rights: List['AppRight'] = Relationship(back_populates='user')
40
+
41
+
42
+ def user_convertor(df: pd.DataFrame) -> List[Dict]:
43
+ """
44
+ Dummy user convertor
45
+ """
46
+ return [x for _, x in df.iterrows()]
47
+
48
+
49
+ def user_reductor(in_db_row: AppUser, db_row: AppUser) -> AppUser:
50
+ """
51
+ Update an existing in_db_row with new information coming from db_row
52
+
53
+ NB: in the future this will maybe handle the client as well
54
+ """
55
+ in_db_row.permission = db_row.permission
56
+ in_db_row.password = db_row.password
57
+ return in_db_row
58
+
59
+
60
+ def user_selector(db_row: AppUser) -> SelectOfScalar:
61
+ """
62
+ Criteria on which to decide whether creating a new row or updating an existing one in db
63
+ """
64
+ return select(AppUser).where(col(AppUser.user) == db_row.user)
65
+
66
+
67
+ USER_INSERTOR = Insertor(convertor=user_convertor, selector=user_selector,
68
+ reductor=user_reductor, db_schema=AppUser, read_excel_file=False)
69
+
70
+
71
+ def upsert_app_users(file_path: Path, session: Session) -> None:
72
+ """
73
+ Upsert db users with a list of users provided in the file_path (json format)
74
+ """
75
+ for user in load_json_file(file_path):
76
+ session.add(create_or_update(session, user, USER_INSERTOR))
77
+ session.commit()
78
+
79
+
80
+ def select_user(username: str, session: Session) -> AppUser:
81
+ """
82
+ Helper function to (attempt to) retrieve AppUser from username.
83
+
84
+ NB: Used to check whether user exists, before resetting its password.
85
+ I.e. User does not yet have a token - we are simply checking if
86
+ an active account exists under that username.
87
+
88
+ Raises:
89
+ sqlalchemy.exc.NoResultFound: Typical error is no users are found.
90
+ sqlalchemy.exc.MultipleResultsFound: Should normally never be an issue.
91
+ """
92
+ return session.exec(select(AppUser).where(col(AppUser.user) == username)).one()
@@ -0,0 +1,18 @@
1
+ """
2
+ Module implementing authentication configuration.
3
+ """
4
+ from pydantic_settings import BaseSettings
5
+ from pydantic_settings import SettingsConfigDict
6
+
7
+
8
+ class AuthenticationConfiguration(BaseSettings):
9
+ """
10
+ Simple authentication configuration class
11
+ """
12
+ secret_key: str
13
+ algorithm: str
14
+ access_token_expire_minutes: int
15
+ model_config = SettingsConfigDict(env_file='.env')
16
+
17
+
18
+ AUTH = AuthenticationConfiguration()