liberty-framework 6.0.44__py3-none-any.whl → 6.0.46__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.
@@ -76,4 +76,6 @@ class ApiController:
76
76
 
77
77
  async def ai_welcome(self, req: Request):
78
78
  return await self.apiRest.ai_welcome(req)
79
-
79
+
80
+ async def rest(self, req: Request):
81
+ return await self.apiRest.rest(req)
@@ -37,4 +37,10 @@ class SetupController:
37
37
  return self.alembic.revision(req)
38
38
 
39
39
  def current(self, req: Request):
40
- return self.alembic.current(req)
40
+ return self.alembic.current(req)
41
+
42
+ async def create(self, req: Request):
43
+ return await self.setupRest.create_database(req)
44
+
45
+ async def drop(self, req: Request):
46
+ return await self.setupRest.drop_database(req)
@@ -201,3 +201,6 @@
201
201
  {"timestamp": "2025-02-20T09:15:08.302464+00:00", "transactionName": "QueryDAO.applications", "level": "info", "method": "GET", "url": "http://localhost:5173/api/fmw/applications", "data": {"message": "Request failed with status code 500: undefined"}, "message": "Framework: Load Applications", "category": "http", "feature": "database-api", "isException": true}
202
202
  {"timestamp": "2025-02-20T09:15:08.303872+00:00", "transactionName": "loginUtils.getApplications", "level": "info", "method": null, "url": null, "data": {"status": "error", "items": [{"message": "Request failed with status code 500: undefined"}]}, "message": "Login: Failed to fetch applications", "category": "debug", "feature": "console", "isException": true}
203
203
  {"timestamp": "2025-02-20T09:20:53.819597+00:00", "transactionName": "QueryDAO.login", "level": "info", "method": "GET", "url": "http://localhost:5173/api/auth/user?user=admin&pool=libnsx1&mode=session", "data": {"message": "Request failed with status code 500: Error creating pool: Error creating pool: [Errno 8] nodename nor servname provided, or not known"}, "message": "User Login: Get User Properties", "category": "http", "feature": "database-api", "isException": true}
204
+ {"timestamp": "2025-02-26T15:16:17.684769+00:00", "transactionName": "APIDAO.post", "level": "info", "method": "POST", "url": "http://localhost:5173/api/rest?pool=default&mode=session&api=1", "data": null, "message": "REST: Failed POST API call", "category": "http", "feature": "rest-api", "isException": true}
205
+ {"timestamp": "2025-02-26T16:00:40.041233+00:00", "transactionName": "APIDAO.post", "level": "info", "method": "POST", "url": "http://localhost:5173/api/rest?pool=default&mode=session&api=1", "data": null, "message": "REST: Failed POST API call", "category": "http", "feature": "rest-api", "isException": true}
206
+ {"timestamp": "2025-02-26T16:27:09.583000+00:00", "transactionName": "APIDAO.post", "level": "info", "method": "POST", "url": "http://localhost:5173/api/rest?pool=default&mode=session&api=1", "data": null, "message": "REST: Failed POST API call", "category": "http", "feature": "rest-api", "isException": true}
@@ -810,3 +810,15 @@ Category: debug, Feature: console, IsException: True
810
810
  Method: GET, URL: http://localhost:5173/api/auth/user?user=admin&pool=libnsx1&mode=session
811
811
  Category: http, Feature: database-api, IsException: True
812
812
 
813
+ [2025-02-26T15:16:17.684769+00:00] [info] APIDAO.post - REST: Failed POST API call
814
+ Method: POST, URL: http://localhost:5173/api/rest?pool=default&mode=session&api=1
815
+ Category: http, Feature: rest-api, IsException: True
816
+
817
+ [2025-02-26T16:00:40.041233+00:00] [info] APIDAO.post - REST: Failed POST API call
818
+ Method: POST, URL: http://localhost:5173/api/rest?pool=default&mode=session&api=1
819
+ Category: http, Feature: rest-api, IsException: True
820
+
821
+ [2025-02-26T16:27:09.583000+00:00] [info] APIDAO.post - REST: Failed POST API call
822
+ Method: POST, URL: http://localhost:5173/api/rest?pool=default&mode=session&api=1
823
+ Category: http, Feature: rest-api, IsException: True
824
+
app/models/setup.py CHANGED
@@ -14,4 +14,31 @@ SETUP_RESPONSE_EXAMPLE = {
14
14
  "items": [],
15
15
  "status": "success",
16
16
  "count": 0
17
- }
17
+ }
18
+
19
+ class CreateRequest(BaseModel):
20
+ host: str
21
+ port: int
22
+ database: str
23
+ user: str
24
+ password: str
25
+
26
+ CREATE_ERROR_MESSAGE = "Create database failed"
27
+ CREATE_RESPONSE_DESCRIPTION = "Create database successful"
28
+ CREATE_RESPONSE_EXAMPLE = {
29
+ "items": [],
30
+ "status": "success",
31
+ "count": 0
32
+ }
33
+
34
+ class DropRequest(BaseModel):
35
+ database: str
36
+ user: str
37
+
38
+ DROP_ERROR_MESSAGE = "Drop database failed"
39
+ DROP_RESPONSE_DESCRIPTION = "Drop Database successful"
40
+ DROP_RESPONSE_EXAMPLE = {
41
+ "items": [],
42
+ "status": "success",
43
+ "count": 0
44
+ }
app/routes/api_routes.py CHANGED
@@ -230,7 +230,7 @@ def setup_api_routes(app, controller: ApiController, jwt: JWT):
230
230
  ):
231
231
  return await controller.close(req)
232
232
 
233
-
233
+
234
234
  @router.get(
235
235
  "/db/query",
236
236
  response_model=GetSuccessResponse, # Specify the success response schema
@@ -426,4 +426,66 @@ def setup_api_routes(app, controller: ApiController, jwt: JWT):
426
426
  return await controller.ai_welcome(req)
427
427
 
428
428
 
429
+ @router.post(
430
+ "/rest",
431
+ response_model=PostSuccessResponse, # Specify the success response schema
432
+ responses={
433
+ 200: response_200(PostSuccessResponse, POST_APIDB_RESPONSE_DESCRIPTION, POST_APIDB_RESPONSE_EXAMPLE),
434
+ 400: response_400("Request body cannot be empty."),
435
+ 422: response_422(),
436
+ 500: response_500(PostErrorResponse, POST_APIDB_ERROR_EXAMPLE),
437
+ },
438
+ summary="REST - Call Post Api",
439
+ description="Call a rest api (post).",
440
+ tags=["Query"],
441
+ )
442
+ async def post(
443
+ req: Request,
444
+ jwt: str = Depends(jwt.is_valid_jwt),
445
+ source: QuerySource = Query(None, description="The source to retrieve the query definition. Valid values: `framework`, `query`"),
446
+ type: QueryType = Query(None, description="The type of query, get data or metadata. Valid values: `table`, `columns`."),
447
+ pool: str = Query(None, description="The database pool alias to retrieve the query definition. (e.g., `default`, `libnsx1`)"),
448
+ mode: SessionMode = Query(None, description="The session mode, retrieve data from framework table or pool. Valid values: `framework`, `session`"),
449
+ query: int = Query(None, description="The query ID to execute. (e.g., `1`, `2`)"),
450
+ override_pool: Optional[str] = Query(None, description="Override the default pool set in the query definition. (e.g., `default`, `libnsx1`)"),
451
+ body: Dict[str, Any] = Body(..., description="JSON object with key-value pairs is required.")
452
+ ):
453
+ if not body: # Check if the body is empty
454
+ raise HTTPException(
455
+ status_code=400,
456
+ detail="Request body cannot be empty. JSON object with key-value pairs is required.",
457
+ )
458
+ return await controller.rest(req)
459
+
460
+ @router.get(
461
+ "/rest",
462
+ response_model=PostSuccessResponse, # Specify the success response schema
463
+ responses={
464
+ 200: response_200(PostSuccessResponse, POST_APIDB_RESPONSE_DESCRIPTION, POST_APIDB_RESPONSE_EXAMPLE),
465
+ 400: response_400("Request body cannot be empty."),
466
+ 422: response_422(),
467
+ 500: response_500(PostErrorResponse, POST_APIDB_ERROR_EXAMPLE),
468
+ },
469
+ summary="REST - Call Post Api",
470
+ description="Call a rest api (post).",
471
+ tags=["Query"],
472
+ )
473
+ async def get(
474
+ req: Request,
475
+ jwt: str = Depends(jwt.is_valid_jwt),
476
+ source: QuerySource = Query(None, description="The source to retrieve the query definition. Valid values: `framework`, `query`"),
477
+ type: QueryType = Query(None, description="The type of query, get data or metadata. Valid values: `table`, `columns`."),
478
+ pool: str = Query(None, description="The database pool alias to retrieve the query definition. (e.g., `default`, `libnsx1`)"),
479
+ mode: SessionMode = Query(None, description="The session mode, retrieve data from framework table or pool. Valid values: `framework`, `session`"),
480
+ query: int = Query(None, description="The query ID to execute. (e.g., `1`, `2`)"),
481
+ override_pool: Optional[str] = Query(None, description="Override the default pool set in the query definition. (e.g., `default`, `libnsx1`)"),
482
+ body: Dict[str, Any] = Body(..., description="JSON object with key-value pairs is required.")
483
+ ):
484
+ if not body: # Check if the body is empty
485
+ raise HTTPException(
486
+ status_code=400,
487
+ detail="Request body cannot be empty. JSON object with key-value pairs is required.",
488
+ )
489
+ return await controller.rest(req)
490
+
429
491
  app.include_router(router, prefix="/api")
@@ -8,7 +8,7 @@ from fastapi import APIRouter, Request
8
8
 
9
9
  from app.controllers.setup_controller import SetupController
10
10
  from app.models.base import ErrorResponse, SuccessResponse, response_200, response_422, response_500
11
- from app.models.setup import SETUP_ERROR_MESSAGE, SETUP_RESPONSE_DESCRIPTION, SETUP_RESPONSE_EXAMPLE, SetupRequest
11
+ from app.models.setup import CREATE_ERROR_MESSAGE, CREATE_RESPONSE_DESCRIPTION, CREATE_RESPONSE_EXAMPLE, DROP_ERROR_MESSAGE, DROP_RESPONSE_DESCRIPTION, DROP_RESPONSE_EXAMPLE, SETUP_ERROR_MESSAGE, SETUP_RESPONSE_DESCRIPTION, SETUP_RESPONSE_EXAMPLE, CreateRequest, DropRequest, SetupRequest
12
12
 
13
13
 
14
14
  def setup_setup_routes(app, controller: SetupController):
@@ -205,4 +205,42 @@ def setup_setup_routes(app, controller: SetupController):
205
205
  ):
206
206
  return controller.current(req)
207
207
 
208
- app.include_router(router, prefix="/api")
208
+
209
+ @router.post(
210
+ "/db/create",
211
+ summary="DATABASE - Create",
212
+ description="Create database for new application",
213
+ tags=["Database"],
214
+ response_model=SuccessResponse,
215
+ responses={
216
+ 200: response_200(SuccessResponse, CREATE_RESPONSE_DESCRIPTION, CREATE_RESPONSE_EXAMPLE),
217
+ 422: response_422(),
218
+ 500: response_500(ErrorResponse, CREATE_ERROR_MESSAGE),
219
+ },
220
+ )
221
+ async def create(
222
+ req: Request,
223
+ body: CreateRequest,
224
+ ):
225
+ return await controller.create(req)
226
+
227
+ @router.post(
228
+ "/db/drop",
229
+ summary="DATABASE - Drop",
230
+ description="Drop an existing database",
231
+ tags=["Database"],
232
+ response_model=SuccessResponse,
233
+ responses={
234
+ 200: response_200(SuccessResponse, DROP_RESPONSE_EXAMPLE, DROP_RESPONSE_DESCRIPTION),
235
+ 422: response_422(),
236
+ 500: response_500(ErrorResponse, DROP_ERROR_MESSAGE),
237
+ },
238
+ )
239
+ async def drop(
240
+ req: Request,
241
+ body: DropRequest,
242
+ ):
243
+ return await controller.drop(req)
244
+
245
+ app.include_router(router, prefix="/api")
246
+
app/services/api_rest.py CHANGED
@@ -1,11 +1,15 @@
1
1
  # Description: API REST service for handling REST API requests.
2
2
  import logging
3
3
  import os
4
+ import re
5
+ from urllib.parse import urljoin, urlparse
4
6
 
7
+ from fastapi.responses import JSONResponse
5
8
  import httpx
6
9
  from pydantic import BaseModel
7
10
 
8
- from app.services.db_query import Query
11
+ from app.services.db_query import Query, SessionMode
12
+ from app.utils.encrypt import Encryption
9
13
  logger = logging.getLogger(__name__)
10
14
 
11
15
  import json
@@ -14,14 +18,12 @@ from datetime import datetime, timezone
14
18
  from app.utils.logs import LogHandler
15
19
  from app.logs import get_logs_json_path, get_logs_text_path
16
20
 
21
+ defaultPool = "default"
22
+
17
23
  class ApiType:
18
24
  internal = "INTERNAL"
19
25
  external = "EXTERNAL"
20
26
 
21
- class ApiFramework:
22
- CreateFrameworkDatabase = "CreateFrameworkDatabase"
23
- DropFrameworkDatabase = "DropFrameworkDatabase"
24
-
25
27
  class AIResponse(BaseModel):
26
28
  message: str
27
29
  is_truncated: bool
@@ -32,6 +34,95 @@ class Rest:
32
34
  self.logs_handler = LogHandler()
33
35
  self.queryRest = queryRest
34
36
 
37
+ async def rest(self, req: Request):
38
+ try:
39
+ query_params = req.query_params
40
+
41
+ """Extracts OpenAI API URL and Key from MODULE_ID = 'AI'."""
42
+ query = {
43
+ "QUERY": 34,
44
+ "POOL": query_params.get("mode") == SessionMode.framework and defaultPool or query_params.get("pool"),
45
+ "CRUD": "GET",
46
+ }
47
+ context = {
48
+ "row_offset": 0,
49
+ "row_limit": 1000,
50
+ "where": {"API_ID":query_params.get("api")},
51
+ }
52
+ # Get the target query using the framework query method
53
+ target_query = await self.queryRest.db_pools.get_pool("default").db_dao.get_framework_query(
54
+ query, self.queryRest.db_pools.get_pool("default").db_type
55
+ )
56
+
57
+ api = await self.queryRest.db_pools.get_pool("default").db_dao.get(target_query, context)
58
+ rows = api.get("rows")
59
+
60
+ if not api.get("rows"):
61
+ raise ValueError("No API found")
62
+
63
+ row = rows[0] # Extract the first row (dictionary)
64
+
65
+ api_type = row.get("API_SOURCE")
66
+ method = row.get("API_METHOD")
67
+ url = row.get("API_URL")
68
+ user = row.get("API_USER")
69
+ password = row.get("API_PASSWORD")
70
+ body = row.get("API_BODY")
71
+
72
+ # Convert API_BODY from JSON string format to a Python dictionary
73
+ body_dict = json.loads(body)
74
+
75
+ # 🔹 Ensure request body is retrieved properly
76
+ req_body = await req.json()
77
+
78
+ # Perform variable substitution in body
79
+ body_str = json.dumps(body_dict) # Convert dictionary to string for replacement
80
+ for key, value in req_body.items():
81
+ variable = rf"\${key.upper()}"
82
+ body_str = re.sub(variable, str(value), body_str)
83
+
84
+ # Convert back to dictionary
85
+ parsed_body = json.loads(body_str)
86
+ if api_type == ApiType.internal:
87
+ base_url = str(req.base_url)
88
+ full_url = urljoin(base_url, url)
89
+ else:
90
+ # Check if `url` is already a full external URL
91
+ parsed_url = urlparse(url)
92
+ if parsed_url.scheme and parsed_url.netloc:
93
+ full_url = url # Use the full external URL as is
94
+ else:
95
+ raise ValueError(f"Invalid external URL: {url}")
96
+
97
+ # 🔹 Make the API call
98
+ async with httpx.AsyncClient(timeout=60.0) as client:
99
+ response = await client.post(full_url, json=parsed_body)
100
+ if response.status_code == 200:
101
+ response_data = response.json()
102
+ else:
103
+ response_data = {
104
+ "error": f"Failed request with status code {response.status_code}",
105
+ "details": response.text
106
+ }
107
+ response_data = response.json()
108
+
109
+ return JSONResponse({
110
+ "items": response_data,
111
+ "status": "success",
112
+ "count": 0,
113
+ })
114
+ except Exception as err:
115
+ message = str(err)
116
+ return JSONResponse({
117
+ "items": [{"message": f"Error: {message}"}],
118
+ "status": "error",
119
+ "hasMore": False,
120
+ "limit": context.get("row_limit", 1000),
121
+ "offset": context.get("row_offset", 0),
122
+ "count": 0,
123
+ })
124
+
125
+
35
126
 
36
127
  async def push_log(self, req: Request):
37
128
  """
@@ -191,3 +191,7 @@ class Install:
191
191
  except Exception as e:
192
192
  logging.error(f"Update failed: {e}")
193
193
 
194
+
195
+
196
+
197
+
@@ -1,4 +1,6 @@
1
1
  import logging
2
+
3
+ from app.setup.models.liberty import Base
2
4
  logger = logging.getLogger(__name__)
3
5
 
4
6
  from sqlalchemy import create_engine, text
@@ -35,13 +37,6 @@ class Setup:
35
37
  admin_password = data.get("admin_password")
36
38
  load_data = data.get("load_data", False)
37
39
 
38
- # Create all tables in the database
39
- # for table in Base.metadata.tables.values():
40
- # if not table.schema:
41
- # table.schema = database # 🔹 Assign schema to tables
42
- # Base.metadata.create_all(engine)
43
- # logging.warning("All tables have been successfully created!")
44
-
45
40
  # Database configuration
46
41
  ADMIN_DATABASE_URL = f"postgresql+psycopg2://{user}:{current_password}@{host}:{port}/{admin_database}"
47
42
 
@@ -423,4 +418,142 @@ pool_alias=default
423
418
  "items": [{"message": f"Error: {message}"}],
424
419
  "status": "error",
425
420
  "count": 0
426
- })
421
+ })
422
+
423
+
424
+ async def create_database(self, req: Request):
425
+ try:
426
+ data = await req.json()
427
+ host = data.get("host")
428
+ port = data.get("port")
429
+ database = data.get("database")
430
+ user = data.get("user")
431
+ password = data.get("password")
432
+
433
+ # Database configuration
434
+ db_properties_path = get_db_properties_path()
435
+ config = self.apiController.queryRest.load_db_properties(db_properties_path)
436
+ # Database configuration
437
+ ADMIN_DATABASE_URL = f"postgresql+psycopg2://{config["user"]}:{config["password"]}@{config["host"]}:{config["port"]}/{config["database"]}"
438
+
439
+ # Create an engine
440
+ admin_engine = create_engine(ADMIN_DATABASE_URL, isolation_level="AUTOCOMMIT")
441
+ with admin_engine.connect() as conn:
442
+ result = conn.execute(text(f"SELECT 1 FROM pg_database WHERE datname = '{database}'"))
443
+ db_exists = result.scalar()
444
+
445
+ if not db_exists:
446
+ logging.warning(f"Creating database '{database}'...")
447
+ conn.execute(text(f'CREATE DATABASE "{database}"'))
448
+ else:
449
+ logging.warning(f"Database '{database}' already exists. Skipping creation.")
450
+ # 🚀 Check if the role exists
451
+ result = conn.execute(text(f"SELECT 1 FROM pg_roles WHERE rolname = '{user}'"))
452
+ role_exists = result.scalar()
453
+
454
+ if not role_exists:
455
+ logging.warning(f"Creating role '{user}' with password...")
456
+ conn.execute(text(f"CREATE ROLE {user} WITH LOGIN PASSWORD '{password}'"))
457
+ else:
458
+ logging.warning(f"Role '{user}' already exists. Skipping creation.")
459
+
460
+ # 🚀 Grant privileges to the role
461
+ conn.execute(text(f'GRANT ALL PRIVILEGES ON DATABASE "{database}" TO {user}'))
462
+ logging.warning(f"Granted privileges to role '{user}' on database '{database}'.")
463
+
464
+ # Create all tables in the database
465
+ DATABASE_URL = f"postgresql+psycopg2://{user}:{password}@{host}:{port}/{database}"
466
+ engine = create_engine(DATABASE_URL, echo=False, isolation_level="AUTOCOMMIT")
467
+
468
+ with engine.connect() as conn:
469
+ result = conn.execute(text(f"SELECT 1 FROM information_schema.schemata WHERE schema_name = '{database}'"))
470
+ schema_exists = result.scalar()
471
+
472
+ if not schema_exists:
473
+ logging.warning(f"Creating schema '{user}'...")
474
+ conn.execute(text(f'CREATE SCHEMA "{user}" AUTHORIZATION {user}'))
475
+ else:
476
+ logging.warning(f"Schema '{user}' already exists. Skipping creation.")
477
+
478
+ for table in Base.metadata.tables.values():
479
+ if not table.schema:
480
+ table.schema = database
481
+ Base.metadata.create_all(engine)
482
+
483
+ logging.warning("All tables have been successfully created!")
484
+ # Return the response
485
+ return JSONResponse({
486
+ "items": [],
487
+ "status": "success",
488
+ "count": 0
489
+ })
490
+
491
+ except Exception as err:
492
+ message = str(err)
493
+ return JSONResponse({
494
+ "items": [{"message": f"Error: {message}"}],
495
+ "status": "error",
496
+ "count": 0
497
+ })
498
+
499
+ async def drop_database(self, req: Request):
500
+ try:
501
+ data = await req.json()
502
+ database = data.get("database")
503
+ user = data.get("user")
504
+
505
+ # Database configuration
506
+ db_properties_path = get_db_properties_path()
507
+ config = self.apiController.queryRest.load_db_properties(db_properties_path)
508
+ # Database configuration
509
+ ADMIN_DATABASE_URL = f"postgresql+psycopg2://{config["user"]}:{config["password"]}@{config["host"]}:{config["port"]}/{config["database"]}"
510
+
511
+ # Create an engine
512
+ admin_engine = create_engine(ADMIN_DATABASE_URL, isolation_level="AUTOCOMMIT")
513
+ with admin_engine.connect() as conn:
514
+ # 🚀 Check if database exists
515
+ result = conn.execute(text(f"SELECT 1 FROM pg_database WHERE datname = '{database}'"))
516
+ db_exists = result.scalar()
517
+
518
+ if db_exists:
519
+ logging.warning(f"Revoking new connections to database '{database}'...")
520
+ conn.execute(text(f"UPDATE pg_database SET datallowconn = FALSE WHERE datname = '{database}'"))
521
+
522
+ logging.warning(f"Terminating active connections to database '{database}'...")
523
+ conn.execute(text(f"""
524
+ SELECT pg_terminate_backend(pg_stat_activity.pid)
525
+ FROM pg_stat_activity
526
+ WHERE pg_stat_activity.datname = '{database}'
527
+ AND pid <> pg_backend_pid();
528
+ """))
529
+
530
+ logging.warning(f"Dropping database '{database}'...")
531
+ conn.execute(text(f'DROP DATABASE "{database}"'))
532
+ else:
533
+ logging.warning(f"Database '{database}' does not exist. Skipping drop.")
534
+
535
+ # 🚀 Check if the role exists
536
+ result = conn.execute(text(f"SELECT 1 FROM pg_roles WHERE rolname = '{user}'"))
537
+ role_exists = result.scalar()
538
+
539
+ if role_exists:
540
+ logging.warning(f"Dropping role '{user}'...")
541
+ conn.execute(text(f"DROP ROLE {user}"))
542
+ else:
543
+ logging.warning(f"Role '{user}' does not exist. Skipping drop.")
544
+
545
+ logging.warning("Database successfully dropped!")
546
+ # Return the response
547
+ return JSONResponse({
548
+ "items": [],
549
+ "status": "success",
550
+ "count": 0
551
+ })
552
+
553
+ except Exception as err:
554
+ message = str(err)
555
+ return JSONResponse({
556
+ "items": [{"message": f"Error: {message}"}],
557
+ "status": "error",
558
+ "count": 0
559
+ })
@@ -0,0 +1,224 @@
1
+ Metadata-Version: 2.2
2
+ Name: liberty-framework
3
+ Version: 6.0.46
4
+ Summary: Liberty Framework
5
+ Author: Franck Blettner
6
+ Author-email: franck.blettner@nomana-it.fr
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: Framework :: FastAPI
9
+ Classifier: Operating System :: OS Independent
10
+ Requires-Python: >=3.8
11
+ Description-Content-Type: text/markdown
12
+ License-File: LICENSE
13
+ Requires-Dist: alembic==1.14.1
14
+ Requires-Dist: allure-pytest==2.13.5
15
+ Requires-Dist: allure-python-commons==2.13.5
16
+ Requires-Dist: annotated-types==0.7.0
17
+ Requires-Dist: anyio==4.8.0
18
+ Requires-Dist: asyncpg==0.30.0
19
+ Requires-Dist: attrs==25.1.0
20
+ Requires-Dist: bidict==0.23.1
21
+ Requires-Dist: certifi==2024.12.14
22
+ Requires-Dist: cffi==1.17.1
23
+ Requires-Dist: click==8.1.8
24
+ Requires-Dist: cryptography==44.0.0
25
+ Requires-Dist: dnspython==2.7.0
26
+ Requires-Dist: email_validator==2.2.0
27
+ Requires-Dist: fastapi==0.115.7
28
+ Requires-Dist: fastapi-cli==0.0.7
29
+ Requires-Dist: fastapi-socketio==0.0.10
30
+ Requires-Dist: greenlet==3.1.1
31
+ Requires-Dist: h11==0.14.0
32
+ Requires-Dist: httpcore==1.0.7
33
+ Requires-Dist: httptools==0.6.4
34
+ Requires-Dist: httpx==0.28.1
35
+ Requires-Dist: idna==3.10
36
+ Requires-Dist: inflect==7.5.0
37
+ Requires-Dist: iniconfig==2.0.0
38
+ Requires-Dist: Jinja2==3.1.5
39
+ Requires-Dist: Mako==1.3.8
40
+ Requires-Dist: markdown-it-py==3.0.0
41
+ Requires-Dist: MarkupSafe==3.0.2
42
+ Requires-Dist: mdurl==0.1.2
43
+ Requires-Dist: more-itertools==10.6.0
44
+ Requires-Dist: oracledb==2.4.1
45
+ Requires-Dist: packaging==24.2
46
+ Requires-Dist: pluggy==1.5.0
47
+ Requires-Dist: psycopg2-binary==2.9.10
48
+ Requires-Dist: pycparser==2.22
49
+ Requires-Dist: pydantic==2.10.6
50
+ Requires-Dist: pydantic_core==2.27.2
51
+ Requires-Dist: Pygments==2.19.1
52
+ Requires-Dist: PyJWT==2.10.1
53
+ Requires-Dist: pytest==8.3.4
54
+ Requires-Dist: python-dotenv==1.0.1
55
+ Requires-Dist: python-engineio==4.11.2
56
+ Requires-Dist: python-multipart==0.0.20
57
+ Requires-Dist: python-socketio==5.12.1
58
+ Requires-Dist: PyYAML==6.0.2
59
+ Requires-Dist: rich==13.9.4
60
+ Requires-Dist: rich-toolkit==0.13.2
61
+ Requires-Dist: setuptools==75.8.0
62
+ Requires-Dist: shellingham==1.5.4
63
+ Requires-Dist: simple-websocket==1.1.0
64
+ Requires-Dist: sniffio==1.3.1
65
+ Requires-Dist: SQLAlchemy==2.0.37
66
+ Requires-Dist: starlette==0.45.3
67
+ Requires-Dist: typeguard==4.4.1
68
+ Requires-Dist: typer==0.15.1
69
+ Requires-Dist: typing_extensions==4.12.2
70
+ Requires-Dist: uvicorn==0.34.0
71
+ Requires-Dist: uvloop==0.21.0
72
+ Requires-Dist: watchfiles==1.0.4
73
+ Requires-Dist: websockets==14.2
74
+ Requires-Dist: wsproto==1.2.0
75
+ Dynamic: author
76
+ Dynamic: author-email
77
+ Dynamic: classifier
78
+ Dynamic: description
79
+ Dynamic: description-content-type
80
+ Dynamic: requires-dist
81
+ Dynamic: requires-python
82
+ Dynamic: summary
83
+
84
+ # 📖 Liberty Framework
85
+ ### A Scalable and Extensible FastAPI and React Framework for Business Applications
86
+
87
+ ## Announcements
88
+ - **Release 6.0.46**: Implement call for custom rest api, add drop and create database for framework
89
+
90
+ 🚀 **Liberty Framework** is a powerful, modular, and extensible **FastAPI-based and React-based framework** designed to streamline backend development for business applications. It provides **database management, authentication, real-time socket communication, and more**, making it easy to deploy and scale enterprise solutions.
91
+
92
+ - Online demo is available at [https://liberty.nomana-it.fr](https://liberty.nomana-it.fr)
93
+ - Web page at: [https://nomana-it.fr](https://nomana-it.fr)
94
+
95
+ ```ini
96
+ Login = demo
97
+ Password = demo
98
+ Appplication = LIBERTY, NOMASX-1 and NOMAJDE
99
+ ```
100
+
101
+ ![liberty](https://github.com/user-attachments/assets/74479874-d7ba-469a-b525-b468529c4432)
102
+
103
+ ---
104
+
105
+ ## ✨ Features
106
+ ✅ **FastAPI-based** – High-performance backend with asynchronous capabilities.
107
+ ✅ **React-based** – Beautiful and responsive frontend without any dependencies to components.
108
+ ✅ **Database Management** – SQLAlchemy, Alembic for migrations, and PostgreSQL support.
109
+ ✅ **Real-Time Communication** – Integrated WebSocket (Socket.IO) for live updates.
110
+ ✅ **Authentication & Security** – JWT authentication, encryption, and role-based access.
111
+ ✅ **Automated Database Migrations** – Alembic versioning for multiple databases.
112
+ ✅ **Easy Installation & Deployment** – Available as a **Python package** and **Docker image**.
113
+ ✅ **Extensible** – Plugin-based architecture to support future enhancements.
114
+
115
+ ## ✨ Enterprise additional features
116
+ - 🌐 **Traefik**: A powerful reverse proxy for routing and load balancing.
117
+ - ⚙️ **AirFlow**: Automate and manage workflows effortlessly.
118
+ - 🐘 **pgAdmin**: Manage your PostgreSQL database visually with ease.
119
+ - 🔐 **KeyCloak**: OIDC Service for authentication
120
+ - 📂 **Gitea**: Git Repository to manage dags, plugins, backup
121
+
122
+ ---
123
+
124
+ ## 📦 Installation
125
+
126
+ ### Requirements
127
+ A PostgreSQL 16 database is required. You can either:
128
+ - Create a Docker image based on postgres:16, or
129
+ - Install PostgreSQL 16 directly on your host system.
130
+
131
+ For easier setup and to ensure compatibility with future enterprise features, it is recommended to create a database with a user named liberty.
132
+
133
+ ### **Option 1: Install via `pip`**
134
+ ```bash
135
+ pip install liberty-framework
136
+ ```
137
+
138
+
139
+ ---
140
+
141
+ ## 🚀 Quick Start
142
+ After installation, you can **start the framework** with:
143
+ ```bash
144
+ liberty-start
145
+ ```
146
+
147
+ ---
148
+
149
+ ## ⚙️ URL
150
+
151
+ ### Installation URL
152
+ - Setup: `http://<your_host>:<your_port>/setup`
153
+ - Application: `http://<your_host>:<your_port>`
154
+ - API Documentation: `http://<your_host>:<your_port>/api`
155
+ - Swagger: `http://<your_host>:<your_port>/api/test`
156
+
157
+ ### Demo URL
158
+ - Setup: [https://liberty.nomana-it.fr/setup](https://liberty.nomana-it.fr/setup)
159
+ - Application: [https://liberty.nomana-it.fr](https://liberty.nomana-it.fr)
160
+ - API Documentation: [https://liberty.nomana-it.fr/api](https://liberty.nomana-it.fr/api)
161
+ - Swagger: [https://liberty.nomana-it.fr/api/test](https://liberty.nomana-it.fr/api/test)
162
+
163
+ ---
164
+
165
+ ## 📖 Documentation
166
+ - **Reference**: [https://docs.nomana-it.fr/liberty](https://docs.nomana-it.fr/liberty)
167
+
168
+ ---
169
+
170
+ ## 🤝 Contributing
171
+ We welcome contributions! Here’s how you can help:
172
+ 1. **Fork** this repository.
173
+ 2. **Clone** your fork:
174
+ ```bash
175
+ git clone https://github.com/fblettner/liberty-framework.git
176
+ ```
177
+ 3. **Create a new branch** for your feature:
178
+ ```bash
179
+ git checkout -b feature-name
180
+ ```
181
+ 4. **Commit your changes**:
182
+ ```bash
183
+ git commit -m "Add new feature"
184
+ ```
185
+ 5. **Push to your fork** and **submit a Pull Request**:
186
+ ```bash
187
+ git push origin feature-name
188
+ ```
189
+ 6. **Join discussions** and help improve the framework!
190
+
191
+ ---
192
+
193
+ ## 💖 Sponsorship
194
+ If you find **Liberty Framework** useful and would like to support its development, consider sponsoring us. Your contributions help maintain the project, add new features, and improve the documentation. Every contribution, big or small, is greatly appreciated!
195
+
196
+ To sponsor, visit: **[GitHub Sponsors](https://github.com/sponsors/fblettner)** or reach out to us directly.
197
+
198
+ ---
199
+
200
+ ## 📜 License
201
+ Liberty Framework is **open-source software** licensed under the **AGPL License**.
202
+ Enterprise features require a license:
203
+ - **NOMASX-1**: Security management, Segregation of duties and licenses compliancy
204
+ - **NOMAJDE** JD-Edwards integration
205
+ - **Airflow Plugins**: Automatic database backup, database synchronisation...
206
+ - **Liberty AI**: Currently, OpenAI is set into the configuration, you have to use your own account without enterprise features license
207
+
208
+ ---
209
+
210
+ ## 📧 Contact & Support
211
+ If you have questions or need support:
212
+ - **Email**: [franck.blettner@nomana-it.fr](mailto:franck.blettner@nomana-it.fr)
213
+ - **GitHub Issues**: [Report an issue](https://github.com/fblettner/liberty-framework/issues)
214
+ - **Discussions**: Join the conversation in the **GitHub Discussions** section.
215
+
216
+ ---
217
+
218
+ ### ⭐ If you find Liberty Framework useful, consider giving it a star on GitHub!
219
+ ```bash
220
+ git clone https://github.com/fblettner/liberty-framework.git
221
+ cd liberty-framework
222
+ ```
223
+
224
+ 🚀 **Let's build the future of business applications together!** 🚀
@@ -30,13 +30,13 @@ app/config/__pycache__/__init__.cpython-312.pyc,sha256=sHDvTIPFsnK8jrKWQauAwMc7j
30
30
  app/config/__pycache__/config.cpython-312.pyc,sha256=dJDpQHukSNdXJe8znD3JmjJjwvfGlDlNCcvG4JwJOrY,769
31
31
  app/config/files/liberty.ini,sha256=otQotGlvSYtBGiTEcDhDLNqlmxp2yxYNxWoxPOVOTvo,558
32
32
  app/controllers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
33
- app/controllers/api_controller.py,sha256=K6r52QIx--HDkdhmNd2MjwaZde3J7L6x9rh_KzuumDU,2618
34
- app/controllers/setup_controller.py,sha256=PX4eOYxLTyXVj0PJy4KlV684QAO0CRF37vEYRBTVvXE,1312
33
+ app/controllers/api_controller.py,sha256=QEJ2lecgVgU4HpMlQkYMeJkFsDQ1khXmDo8oj57RT2c,2712
34
+ app/controllers/setup_controller.py,sha256=YfrLnlTCK_isIOZbVbHxph95Ma7mTfmsoUBvdWqfzL8,1528
35
35
  app/controllers/socket_controller.py,sha256=ok5nbibPEeV4oVx1i_tzoecZwXNocgIUe-ECb_pFAVc,9228
36
36
  app/controllers/__pycache__/__init__.cpython-312.pyc,sha256=x4fclq5ptyLRFD1BLp3RPqwh_g1g3NR0v1DI_Y1ofgg,209
37
- app/controllers/__pycache__/api_controller.cpython-312.pyc,sha256=q2mxU8KEVZeaZwoZ-XXn6WwxW9xSfLnq8XYs-4_0bcY,6360
37
+ app/controllers/__pycache__/api_controller.cpython-312.pyc,sha256=Q-tJJ8I3Vj6DyrXZ8bTGaYvouw83w9us419veG_3yzU,6606
38
38
  app/controllers/__pycache__/react_controller.cpython-312.pyc,sha256=l7_oBeQbJuevc9pd0qZgK7C3QCuOAgR8q2bR_id9xfI,744
39
- app/controllers/__pycache__/setup_controller.cpython-312.pyc,sha256=NQ9lEveld8r7_sgr1vABB_9qVkTkUw9bV5rfrR9kISc,3144
39
+ app/controllers/__pycache__/setup_controller.cpython-312.pyc,sha256=Lw-miWD6XqR996eRAxDaxODqfzJTcg_xz3kfaP59dLw,3666
40
40
  app/controllers/__pycache__/socket_controller.cpython-312.pyc,sha256=wKhC-tGf9VOfL2QpfFK-LIwFWHkoSLlfsaYG111SVLk,12191
41
41
  app/database/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
42
42
  app/database/base_dao.py,sha256=eSZx21R3sTsjWUH9AxOg5nyDa0REzDIzR8pwb5Dh-eg,20275
@@ -54,8 +54,8 @@ app/logs/.DS_Store,sha256=ZGQO-xjQOCQsmmfWoWR3ZEogZ9oYhCs_x2KGpHyB8UQ,6148
54
54
  app/logs/__init__.py,sha256=xjkrhY9xSUg41sFGl2_tuOWANnaP4fYX8RQAn3NvAFs,514
55
55
  app/logs/__pycache__/__init__.cpython-312.pyc,sha256=aGm5f9YknIc8Ip1C4XXk1Of1BvnlqXD16oSHkZSmKRA,889
56
56
  app/logs/__pycache__/logs.cpython-312.pyc,sha256=yQEGRGLwe5bK9jD1AaDCdEsEGLO_76eM7V2WQrn2_20,885
57
- app/logs/files/logs-frontend-json.log,sha256=uk0HsZ9NelR0za2e0Y5lUJJTnQHZCbTCVH1tRUETtok,128568
58
- app/logs/files/logs-frontend-text.log,sha256=1iPyR-oas9-85MnuU35uTYt2_k9goeyrZm20UsK4wbI,43543
57
+ app/logs/files/logs-frontend-json.log,sha256=hrkCwoPzqTvUJNej4oFRPIlCXFY0A4HR3J96FiAwTDE,129501
58
+ app/logs/files/logs-frontend-text.log,sha256=71AD1hm0j_8SMHw4FCL2xVuv_e3YCHRNNUKMydh-yzE,44200
59
59
  app/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
60
60
  app/models/ai.py,sha256=x9QhDHeVly0Paa-GZhkpJBZegS0sZQMmVeJAzAtzmLI,241
61
61
  app/models/apidb.py,sha256=OmOVGSc1r1_sK-CzehlPDeo-Pq2H1yT8EUVsiXPizo4,3556
@@ -64,7 +64,7 @@ app/models/auth.py,sha256=l2gDyUUG3JYJs51VAs8AEg9fmwaQEA10Rq8iqv-vO-Q,1596
64
64
  app/models/base.py,sha256=BApFmQrW-jdGLvbE4pAP2IgDfDBv0esghcZzhWINoqI,2955
65
65
  app/models/modules.py,sha256=nsNeMUO5gTjDJl2YoZXm2Sj85gE2O5Jvh12hIdoP-38,2044
66
66
  app/models/pool.py,sha256=WZAzz3gbqBx7XBY_su1OoyROjE1pwSD9q4eRDtE9io4,391
67
- app/models/setup.py,sha256=CyiZXH-hntu9E1lSPNZhFVRwDYReM1xwTKRz6PP9074,325
67
+ app/models/setup.py,sha256=BycjwHIbSAl-e_aCqjdmZH3rHFhyFiHcpnZ7BOB02js,887
68
68
  app/models/themes.py,sha256=XTCpc2gGF-lg3k8abo_xsneTv3eXTnfHEEYpNLmpdd8,1272
69
69
  app/models/__pycache__/__init__.cpython-312.pyc,sha256=THrpQ910DdudL2c8KyLUSgCfpnxMxWh8FV_mHj_qbIw,173
70
70
  app/models/__pycache__/ai.cpython-312.pyc,sha256=gVvFXfL65eXGhqSBIkhROTPRPGkhNOWZh1D-9URVUgQ,437
@@ -75,7 +75,7 @@ app/models/__pycache__/base.cpython-312.pyc,sha256=VoZV64R0hqGT2lt5-lo1iMw0LWzTg
75
75
  app/models/__pycache__/checkdb.cpython-312.pyc,sha256=xGJ9pEE-GZXiGZu2d-6cNWGt0SKGGevb1LSvWJmqtVY,1741
76
76
  app/models/__pycache__/modules.cpython-312.pyc,sha256=Cp9q_eirwSElqIQMxhBuptwfgwXDbQfc9KeyCTywz-A,2244
77
77
  app/models/__pycache__/pool.cpython-312.pyc,sha256=mFowin-5N38BbcS-ULmIE-_vCoHV8ND6swQ3rZV-2YA,552
78
- app/models/__pycache__/setup.cpython-312.pyc,sha256=oOTCxRorwMK0IUpBc3_uG_ytkKTSpnmEAuyMsGX5n0c,731
78
+ app/models/__pycache__/setup.cpython-312.pyc,sha256=ZO1VHzCcSJdaTJy9RU9jPjVkHrFq-XCrEUvZELX_ZJM,1526
79
79
  app/models/__pycache__/themes.cpython-312.pyc,sha256=v6YJHjoKtlbn8gLr4nGnxeuizPi-kJB8cAsoFMkbUTk,1810
80
80
  app/postgres/.DS_Store,sha256=1PsY3kXXvWpzmt2RSYnlNMGI6I9r1N0MJWuRml4yjo8,8196
81
81
  app/postgres/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -112,22 +112,22 @@ app/public/setup/setup/assets/index-BnpY-7P6.js,sha256=qwUSyqToiQpJw0zkLdZ2dnzXv
112
112
  app/public/setup/setup/assets/index-BnpY-7P6.js.map,sha256=SxHf_DbBfePZXzpvij_EtZmQcF1NnORVo3hGrW2h1_I,3133938
113
113
  app/public/setup/setup/assets/logo_ly-HGj2PB94.svg,sha256=2xnOaSTVYAYU2-ARq5iO80PsKpQveFONztGCoFHl5TU,1466
114
114
  app/routes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
115
- app/routes/api_routes.py,sha256=LrSW_PY2rOR82MeU1PKmF6hZI_cmv4WUvpSKLJwWz58,20477
115
+ app/routes/api_routes.py,sha256=8c4aLp7pqewr3VsZf2tpENnsUk7fGj4ah5uukl6qFis,24125
116
116
  app/routes/react_routes.py,sha256=mO52Juyz3SDXHxCKKrRHSN33e-CeJjlEfxqBn-odGo8,1418
117
- app/routes/setup_routes.py,sha256=FHsN9bt0qI7bERDjDYvV6ur0BdQuYAMQRRlHa_7GbX4,7090
117
+ app/routes/setup_routes.py,sha256=OzMzpEqRXM-srXGmGdsiMelQ-jgqt3RUHj1rfqLHdVc,8391
118
118
  app/routes/socket_routes.py,sha256=DjHh03aEFPKWaMPkzjAmrl3jxQ9TyWSdhyTcDzIjdXk,560
119
119
  app/routes/__pycache__/__init__.cpython-312.pyc,sha256=Y2jZB16q6GIgs_2KUkT_KQELK64DcIovLJna2lGNqmc,204
120
- app/routes/__pycache__/api_routes.cpython-312.pyc,sha256=F_FA8WMgwkpWJ0hkWZuCXC-emw2trqbEzeCZK9aW_bc,21353
120
+ app/routes/__pycache__/api_routes.cpython-312.pyc,sha256=hkUKzPrtdzIxoxlO8hGxQ0FkTQMeRKP0nTaY7LvTRL0,23576
121
121
  app/routes/__pycache__/react_routes.cpython-312.pyc,sha256=5iiOkojr1yV68ORi_dtOhJIAlrMkICjN5AxOHCWTGpQ,2302
122
- app/routes/__pycache__/setup_routes.cpython-312.pyc,sha256=65fnKvkjuIh8LwASnJ0Hr1XUqEY-ag_wQqXplKsqrlw,8615
122
+ app/routes/__pycache__/setup_routes.cpython-312.pyc,sha256=zF4YBdXOKZFPIwEnN703SQkroFbJSn69HYLDc4GO7kM,10105
123
123
  app/routes/__pycache__/socket_routes.cpython-312.pyc,sha256=T46m7FBCH1DxdBIVjyQbd_geYdQRo9O_ZpXNvlghHT8,1419
124
124
  app/services/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
125
- app/services/api_rest.py,sha256=4-tJtpQMIvPL3V5F5FR6qQQsM3fZatYiNk8uhYUbsbc,8675
125
+ app/services/api_rest.py,sha256=w-dIFj-cVGYLNxWTNwjkTibdBX0isK8jeihAwPtv06k,12295
126
126
  app/services/db_pool.py,sha256=gLWCEQ7Pq_6gKoChHJkQH7YmnZoNEMrN6P1MJUiAUec,2043
127
127
  app/services/db_query.py,sha256=IoLrWI4JmZjGP4GgswmXih0IRpvRs7SNNfqREjbrWtE,27475
128
128
  app/services/__pycache__/__init__.cpython-312.pyc,sha256=mcbX3fcnuuxC4RYKQ4PrUp2hLOg-29jJQ8XvXvLoxhw,206
129
129
  app/services/__pycache__/alembic.cpython-312.pyc,sha256=rIS4OWJ4gPJjrUkyalK7wcQ3mWGXTnKZ_OprVObmJ9k,3036
130
- app/services/__pycache__/api_rest.cpython-312.pyc,sha256=nYfBCBnJ9GmR7xiJI8yf1zzHbVkrf2fF8BtTzA-h6vY,12148
130
+ app/services/__pycache__/api_rest.cpython-312.pyc,sha256=YnFtnoPzwcR6IFa9lqJtggZjUXrDQxSGwKnpcHqDIFk,16206
131
131
  app/services/__pycache__/db_pool.cpython-312.pyc,sha256=C9NB4znvWLa_kTgbwDK2xBVQ7VR4DLPbVHtHrNTISmA,4076
132
132
  app/services/__pycache__/db_query.cpython-312.pyc,sha256=1A1An2UJ7DyUbPEatKQEFPKDHW6XiM5TLuBELjXGKDI,32790
133
133
  app/setup/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -154,16 +154,16 @@ app/setup/models/__pycache__/nomasx1.cpython-312.pyc,sha256=4FwPwbwppSeahC7_rGPR
154
154
  app/setup/services/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
155
155
  app/setup/services/alembic.py,sha256=EoV5Cf_TjutwFKLFquFTAzG-Pfhbd8imlcTL4YkvSsA,3144
156
156
  app/setup/services/dump.py,sha256=7pJwwB351kosN-4WHkSagKYL5fzGwCYc6YROovRzQI4,7552
157
- app/setup/services/install.py,sha256=J6xdy1KSTdG21cmKDKdqsczg8N2pIFI387Dv7zzpJQc,8768
157
+ app/setup/services/install.py,sha256=txzO6KRjzc1J7tX2Z8IR6I3DscFtr9TRcxKJDX5uFYM,8779
158
158
  app/setup/services/models.py,sha256=M3FHSDXvFYBdqYNF_3bzYY3UfKXjgy_jQFqkqn-SP-k,10039
159
- app/setup/services/setup.py,sha256=gZdz-_eim4uvFGm4ciTg3whgKICnHTL6a8XXgOU984o,17750
159
+ app/setup/services/setup.py,sha256=dW0G9tRYOdvY7WIy0bxb8qfRX0nXk2BtIBcH9vNxv5c,23852
160
160
  app/setup/services/__pycache__/__init__.cpython-312.pyc,sha256=yjoKcph4MN-OJKFAhvDN9iY2UkBwAeM-aUmJj55Mf2g,180
161
161
  app/setup/services/__pycache__/alembic.cpython-312.pyc,sha256=PIkNJJIC8K_3wPGHBQzL9l1mR5cJ25h4XWNb4lUXnPQ,4638
162
162
  app/setup/services/__pycache__/dump.cpython-312.pyc,sha256=TfXpk2EWcrBeOY-IafRWiTopYdmM2FAMlRWPj9tZIOw,11289
163
163
  app/setup/services/__pycache__/init.cpython-312.pyc,sha256=EiUBfSie7VtB2HOL8Mq1xwCXP29NpynX-eR14fE16xQ,4561
164
- app/setup/services/__pycache__/install.cpython-312.pyc,sha256=dfw5tfzclTl-cP6zHkbYgyDq0BUOm60wRWfQvF47KCA,11979
164
+ app/setup/services/__pycache__/install.cpython-312.pyc,sha256=3XBrnQIgd7Fv5L1zBTwo1zoXZC7MIrZhOZEzT6gUs-w,12026
165
165
  app/setup/services/__pycache__/models.cpython-312.pyc,sha256=ae3uMFVs5G1uOTwqI5dhh-YCwviUlGkSILBQ248gbVc,11084
166
- app/setup/services/__pycache__/setup.cpython-312.pyc,sha256=r2DMNHHPeVD2yfhdMoR35A67yxVNDkKYoeXG4Efc8gA,18825
166
+ app/setup/services/__pycache__/setup.cpython-312.pyc,sha256=0m8Ipuj9aaU4P_9Im4sUdep-oPWLeBMEWjavi_Js71M,26155
167
167
  app/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
168
168
  app/utils/common.py,sha256=UrbbEeqwJ9WXxgTixlOI6wQl06OJBNtEVabiTN77EVc,355
169
169
  app/utils/encrypt.py,sha256=4rPgyIlgZv3LiNl9uWAQafpqvcRV9XB5PW_c0hKXYHo,2819
@@ -203,9 +203,9 @@ tests/scenarios/__pycache__/test_fmw_applications.cpython-312-pytest-8.3.4.pyc,s
203
203
  tests/scenarios/__pycache__/test_fmw_encrypt.cpython-312-pytest-8.3.4.pyc,sha256=VllhpnVjcJtvmJhaagWcDQpzD1GxZ72eFT4yoOy3Bck,2880
204
204
  tests/scenarios/__pycache__/test_fmw_modules.cpython-312-pytest-8.3.4.pyc,sha256=ccuuDE4SD_TRq98nKRwoIBPztSAnVw5RaSoXjeaWQv4,3837
205
205
  tests/scenarios/__pycache__/test_fmw_themes.cpython-312-pytest-8.3.4.pyc,sha256=rfQXJFo9xKCdkxbytBnmLpAKgvk9vW475Ov-3ENBm2o,4900
206
- liberty_framework-6.0.44.dist-info/LICENSE,sha256=ILBn-G3jdarm2w8oOrLmXeJNU3czuJvVhDLBASWdhM8,34522
207
- liberty_framework-6.0.44.dist-info/METADATA,sha256=1JJreOJeYUbpdstmYFWQ3gmsU6zqDL4t08HEM5GGObg,2483
208
- liberty_framework-6.0.44.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
209
- liberty_framework-6.0.44.dist-info/entry_points.txt,sha256=iVkIVLRG-sd7KeOUBSf7Ew_1ckUK5hvZZocaTh3Nq6Q,48
210
- liberty_framework-6.0.44.dist-info/top_level.txt,sha256=MJXn5pCZl0XSEeWNKzshxzMMANr3AymeoMYBO1JNPyU,10
211
- liberty_framework-6.0.44.dist-info/RECORD,,
206
+ liberty_framework-6.0.46.dist-info/LICENSE,sha256=ILBn-G3jdarm2w8oOrLmXeJNU3czuJvVhDLBASWdhM8,34522
207
+ liberty_framework-6.0.46.dist-info/METADATA,sha256=G67TasPY20_huedx7GO_jNCGTTVPZNbPBY61Q_KKpj8,7941
208
+ liberty_framework-6.0.46.dist-info/WHEEL,sha256=nn6H5-ilmfVryoAQl3ZQ2l8SH5imPWFpm1A5FgEuFV4,91
209
+ liberty_framework-6.0.46.dist-info/entry_points.txt,sha256=iVkIVLRG-sd7KeOUBSf7Ew_1ckUK5hvZZocaTh3Nq6Q,48
210
+ liberty_framework-6.0.46.dist-info/top_level.txt,sha256=MJXn5pCZl0XSEeWNKzshxzMMANr3AymeoMYBO1JNPyU,10
211
+ liberty_framework-6.0.46.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.8.0)
2
+ Generator: setuptools (75.8.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,79 +0,0 @@
1
- Metadata-Version: 2.2
2
- Name: liberty-framework
3
- Version: 6.0.44
4
- Summary: Liberty Framework
5
- Author: Franck Blettner
6
- Author-email: franck.blettner@nomana-it.fr
7
- Classifier: Programming Language :: Python :: 3
8
- Classifier: Framework :: FastAPI
9
- Classifier: Operating System :: OS Independent
10
- Requires-Python: >=3.8
11
- License-File: LICENSE
12
- Requires-Dist: alembic==1.14.1
13
- Requires-Dist: allure-pytest==2.13.5
14
- Requires-Dist: allure-python-commons==2.13.5
15
- Requires-Dist: annotated-types==0.7.0
16
- Requires-Dist: anyio==4.8.0
17
- Requires-Dist: asyncpg==0.30.0
18
- Requires-Dist: attrs==25.1.0
19
- Requires-Dist: bidict==0.23.1
20
- Requires-Dist: certifi==2024.12.14
21
- Requires-Dist: cffi==1.17.1
22
- Requires-Dist: click==8.1.8
23
- Requires-Dist: cryptography==44.0.0
24
- Requires-Dist: dnspython==2.7.0
25
- Requires-Dist: email_validator==2.2.0
26
- Requires-Dist: fastapi==0.115.7
27
- Requires-Dist: fastapi-cli==0.0.7
28
- Requires-Dist: fastapi-socketio==0.0.10
29
- Requires-Dist: greenlet==3.1.1
30
- Requires-Dist: h11==0.14.0
31
- Requires-Dist: httpcore==1.0.7
32
- Requires-Dist: httptools==0.6.4
33
- Requires-Dist: httpx==0.28.1
34
- Requires-Dist: idna==3.10
35
- Requires-Dist: inflect==7.5.0
36
- Requires-Dist: iniconfig==2.0.0
37
- Requires-Dist: Jinja2==3.1.5
38
- Requires-Dist: Mako==1.3.8
39
- Requires-Dist: markdown-it-py==3.0.0
40
- Requires-Dist: MarkupSafe==3.0.2
41
- Requires-Dist: mdurl==0.1.2
42
- Requires-Dist: more-itertools==10.6.0
43
- Requires-Dist: oracledb==2.4.1
44
- Requires-Dist: packaging==24.2
45
- Requires-Dist: pluggy==1.5.0
46
- Requires-Dist: psycopg2-binary==2.9.10
47
- Requires-Dist: pycparser==2.22
48
- Requires-Dist: pydantic==2.10.6
49
- Requires-Dist: pydantic_core==2.27.2
50
- Requires-Dist: Pygments==2.19.1
51
- Requires-Dist: PyJWT==2.10.1
52
- Requires-Dist: pytest==8.3.4
53
- Requires-Dist: python-dotenv==1.0.1
54
- Requires-Dist: python-engineio==4.11.2
55
- Requires-Dist: python-multipart==0.0.20
56
- Requires-Dist: python-socketio==5.12.1
57
- Requires-Dist: PyYAML==6.0.2
58
- Requires-Dist: rich==13.9.4
59
- Requires-Dist: rich-toolkit==0.13.2
60
- Requires-Dist: setuptools==75.8.0
61
- Requires-Dist: shellingham==1.5.4
62
- Requires-Dist: simple-websocket==1.1.0
63
- Requires-Dist: sniffio==1.3.1
64
- Requires-Dist: SQLAlchemy==2.0.37
65
- Requires-Dist: starlette==0.45.3
66
- Requires-Dist: typeguard==4.4.1
67
- Requires-Dist: typer==0.15.1
68
- Requires-Dist: typing_extensions==4.12.2
69
- Requires-Dist: uvicorn==0.34.0
70
- Requires-Dist: uvloop==0.21.0
71
- Requires-Dist: watchfiles==1.0.4
72
- Requires-Dist: websockets==14.2
73
- Requires-Dist: wsproto==1.2.0
74
- Dynamic: author
75
- Dynamic: author-email
76
- Dynamic: classifier
77
- Dynamic: requires-dist
78
- Dynamic: requires-python
79
- Dynamic: summary