desdeo 2.0.0__py3-none-any.whl → 2.1.0__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.
- desdeo/adm/ADMAfsar.py +551 -0
- desdeo/adm/ADMChen.py +414 -0
- desdeo/adm/BaseADM.py +119 -0
- desdeo/adm/__init__.py +11 -0
- desdeo/api/__init__.py +6 -6
- desdeo/api/app.py +38 -28
- desdeo/api/config.py +65 -44
- desdeo/api/config.toml +23 -12
- desdeo/api/db.py +10 -8
- desdeo/api/db_init.py +12 -6
- desdeo/api/models/__init__.py +220 -20
- desdeo/api/models/archive.py +16 -27
- desdeo/api/models/emo.py +128 -0
- desdeo/api/models/enautilus.py +69 -0
- desdeo/api/models/gdm/gdm_aggregate.py +139 -0
- desdeo/api/models/gdm/gdm_base.py +69 -0
- desdeo/api/models/gdm/gdm_score_bands.py +114 -0
- desdeo/api/models/gdm/gnimbus.py +138 -0
- desdeo/api/models/generic.py +104 -0
- desdeo/api/models/generic_states.py +401 -0
- desdeo/api/models/nimbus.py +158 -0
- desdeo/api/models/preference.py +44 -6
- desdeo/api/models/problem.py +274 -64
- desdeo/api/models/session.py +4 -1
- desdeo/api/models/state.py +419 -52
- desdeo/api/models/user.py +7 -6
- desdeo/api/models/utopia.py +25 -0
- desdeo/api/routers/_EMO.backup +309 -0
- desdeo/api/routers/_NIMBUS.py +6 -3
- desdeo/api/routers/emo.py +497 -0
- desdeo/api/routers/enautilus.py +237 -0
- desdeo/api/routers/gdm/gdm_aggregate.py +234 -0
- desdeo/api/routers/gdm/gdm_base.py +420 -0
- desdeo/api/routers/gdm/gdm_score_bands/gdm_score_bands_manager.py +398 -0
- desdeo/api/routers/gdm/gdm_score_bands/gdm_score_bands_routers.py +377 -0
- desdeo/api/routers/gdm/gnimbus/gnimbus_manager.py +698 -0
- desdeo/api/routers/gdm/gnimbus/gnimbus_routers.py +591 -0
- desdeo/api/routers/generic.py +233 -0
- desdeo/api/routers/nimbus.py +705 -0
- desdeo/api/routers/problem.py +201 -4
- desdeo/api/routers/reference_point_method.py +20 -44
- desdeo/api/routers/session.py +50 -26
- desdeo/api/routers/user_authentication.py +180 -26
- desdeo/api/routers/utils.py +187 -0
- desdeo/api/routers/utopia.py +230 -0
- desdeo/api/schema.py +10 -4
- desdeo/api/tests/conftest.py +94 -2
- desdeo/api/tests/test_enautilus.py +330 -0
- desdeo/api/tests/test_models.py +550 -72
- desdeo/api/tests/test_routes.py +902 -43
- desdeo/api/utils/_database.py +263 -0
- desdeo/api/utils/database.py +28 -266
- desdeo/api/utils/emo_database.py +40 -0
- desdeo/core.py +7 -0
- desdeo/emo/__init__.py +154 -24
- desdeo/emo/hooks/archivers.py +18 -2
- desdeo/emo/methods/EAs.py +128 -5
- desdeo/emo/methods/bases.py +9 -56
- desdeo/emo/methods/templates.py +111 -0
- desdeo/emo/operators/crossover.py +544 -42
- desdeo/emo/operators/evaluator.py +10 -14
- desdeo/emo/operators/generator.py +127 -24
- desdeo/emo/operators/mutation.py +212 -41
- desdeo/emo/operators/scalar_selection.py +202 -0
- desdeo/emo/operators/selection.py +956 -214
- desdeo/emo/operators/termination.py +124 -16
- desdeo/emo/options/__init__.py +108 -0
- desdeo/emo/options/algorithms.py +435 -0
- desdeo/emo/options/crossover.py +164 -0
- desdeo/emo/options/generator.py +131 -0
- desdeo/emo/options/mutation.py +260 -0
- desdeo/emo/options/repair.py +61 -0
- desdeo/emo/options/scalar_selection.py +66 -0
- desdeo/emo/options/selection.py +127 -0
- desdeo/emo/options/templates.py +383 -0
- desdeo/emo/options/termination.py +143 -0
- desdeo/gdm/__init__.py +22 -0
- desdeo/gdm/gdmtools.py +45 -0
- desdeo/gdm/score_bands.py +114 -0
- desdeo/gdm/voting_rules.py +50 -0
- desdeo/mcdm/__init__.py +23 -1
- desdeo/mcdm/enautilus.py +338 -0
- desdeo/mcdm/gnimbus.py +484 -0
- desdeo/mcdm/nautilus_navigator.py +7 -6
- desdeo/mcdm/reference_point_method.py +70 -0
- desdeo/problem/__init__.py +5 -1
- desdeo/problem/external/__init__.py +18 -0
- desdeo/problem/external/core.py +356 -0
- desdeo/problem/external/pymoo_provider.py +266 -0
- desdeo/problem/external/runtime.py +44 -0
- desdeo/problem/infix_parser.py +2 -2
- desdeo/problem/pyomo_evaluator.py +25 -6
- desdeo/problem/schema.py +69 -48
- desdeo/problem/simulator_evaluator.py +65 -15
- desdeo/problem/testproblems/__init__.py +26 -11
- desdeo/problem/testproblems/benchmarks_server.py +120 -0
- desdeo/problem/testproblems/cake_problem.py +185 -0
- desdeo/problem/testproblems/dmitry_forest_problem_discrete.py +71 -0
- desdeo/problem/testproblems/forest_problem.py +77 -69
- desdeo/problem/testproblems/multi_valued_constraints.py +119 -0
- desdeo/problem/testproblems/{river_pollution_problem.py → river_pollution_problems.py} +28 -22
- desdeo/problem/testproblems/single_objective.py +289 -0
- desdeo/problem/testproblems/zdt_problem.py +4 -1
- desdeo/tools/__init__.py +39 -21
- desdeo/tools/desc_gen.py +22 -0
- desdeo/tools/generics.py +22 -2
- desdeo/tools/group_scalarization.py +3090 -0
- desdeo/tools/indicators_binary.py +107 -1
- desdeo/tools/indicators_unary.py +3 -16
- desdeo/tools/message.py +33 -2
- desdeo/tools/non_dominated_sorting.py +4 -3
- desdeo/tools/patterns.py +9 -7
- desdeo/tools/pyomo_solver_interfaces.py +48 -35
- desdeo/tools/reference_vectors.py +118 -351
- desdeo/tools/scalarization.py +340 -1413
- desdeo/tools/score_bands.py +491 -328
- desdeo/tools/utils.py +117 -49
- desdeo/tools/visualizations.py +67 -0
- desdeo/utopia_stuff/utopia_problem.py +1 -1
- desdeo/utopia_stuff/utopia_problem_old.py +1 -1
- {desdeo-2.0.0.dist-info → desdeo-2.1.0.dist-info}/METADATA +46 -28
- desdeo-2.1.0.dist-info/RECORD +180 -0
- {desdeo-2.0.0.dist-info → desdeo-2.1.0.dist-info}/WHEEL +1 -1
- desdeo-2.0.0.dist-info/RECORD +0 -120
- /desdeo/api/utils/{logger.py → _logger.py} +0 -0
- {desdeo-2.0.0.dist-info → desdeo-2.1.0.dist-info/licenses}/LICENSE +0 -0
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
"""Database utils for the API."""
|
|
2
|
+
|
|
3
|
+
from typing import TypeVar
|
|
4
|
+
|
|
5
|
+
from sqlalchemy.engine import URL, Result
|
|
6
|
+
from sqlalchemy.ext.asyncio import (
|
|
7
|
+
AsyncEngine,
|
|
8
|
+
AsyncScalarResult,
|
|
9
|
+
AsyncSession,
|
|
10
|
+
create_async_engine,
|
|
11
|
+
)
|
|
12
|
+
from sqlalchemy.future import select as sa_select
|
|
13
|
+
from sqlalchemy.orm import DeclarativeMeta, declarative_base, sessionmaker
|
|
14
|
+
from sqlalchemy.pool import NullPool
|
|
15
|
+
from sqlalchemy.sql import Executable
|
|
16
|
+
from sqlalchemy.sql.expression import Delete
|
|
17
|
+
from sqlalchemy.sql.expression import delete as sa_delete
|
|
18
|
+
from sqlalchemy.sql.expression import exists as sa_exists
|
|
19
|
+
from sqlalchemy.sql.functions import count
|
|
20
|
+
from sqlalchemy.sql.selectable import Exists, Select
|
|
21
|
+
|
|
22
|
+
from desdeo.api.config import DatabaseConfig as DBConfig
|
|
23
|
+
|
|
24
|
+
from .logger import get_logger
|
|
25
|
+
|
|
26
|
+
T = TypeVar("T")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def select(entity) -> Select:
|
|
30
|
+
"""Shortcut for :meth:`sqlalchemy.future.select`.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
entity (sqlalchemy.sql.expression.FromClause): Entities / DB model to SELECT from.
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
Select: A Select class object
|
|
37
|
+
"""
|
|
38
|
+
return sa_select(entity)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def filter_by(cls, *args, **kwargs) -> Select:
|
|
42
|
+
"""Shortcut for :meth:`sqlalchemy.future.Select.filter_by`.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
cls (sqlalchemy.sql.expression.FromClause): Entities / DB model to SELECT from.
|
|
46
|
+
*args (tuple): Positional arguments.
|
|
47
|
+
**kwargs (dict): Keyword arguments.
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
Select: A Select class object with a WHERE clause
|
|
51
|
+
"""
|
|
52
|
+
return select(cls, *args).filter_by(**kwargs)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def exists(*entities, **kwargs) -> Exists:
|
|
56
|
+
"""Shortcut for :meth:`sqlalchemy.sql.expression.exists`.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
*entities (tuple): Positional arguments.
|
|
60
|
+
**kwargs (dict): Keyword arguments.
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
Exists: A new Exists construct
|
|
64
|
+
"""
|
|
65
|
+
return sa_exists(*entities, **kwargs)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def delete(table) -> Delete:
|
|
69
|
+
"""Shortcut for :meth:`sqlalchemy.sql.expression.delete`.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
table (sqlalchemy.sql.expression.FromClause): Entities / DB model.
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
Delete: A Delete object
|
|
76
|
+
"""
|
|
77
|
+
return sa_delete(table)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class DB:
|
|
81
|
+
"""An async SQLAlchemy ORM wrapper."""
|
|
82
|
+
|
|
83
|
+
Base: DeclarativeMeta
|
|
84
|
+
_engine: AsyncEngine
|
|
85
|
+
_session: AsyncSession
|
|
86
|
+
|
|
87
|
+
def __init__(self, driver: str, options: dict | None = None, **kwargs):
|
|
88
|
+
"""Init the class.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
driver (str): Drivername for db url.
|
|
92
|
+
options (dict, optional): Options for AsyncEngine instance.
|
|
93
|
+
**kwargs (dict): Keyword arguments.
|
|
94
|
+
"""
|
|
95
|
+
if options is None:
|
|
96
|
+
options = {"pool_size": 20, "max_overflow": 20}
|
|
97
|
+
|
|
98
|
+
url: str = URL.create(drivername=driver, **kwargs)
|
|
99
|
+
self._engine = create_async_engine(url, echo=True, pool_pre_ping=True, pool_recycle=300, **options)
|
|
100
|
+
self.Base = declarative_base()
|
|
101
|
+
self._session: AsyncSession = sessionmaker(self._engine, expire_on_commit=False, class_=AsyncSession)()
|
|
102
|
+
|
|
103
|
+
async def create_tables(self):
|
|
104
|
+
"""Creates all Model Tables."""
|
|
105
|
+
async with self._engine.begin() as conn:
|
|
106
|
+
await conn.run_sync(self.Base.metadata.create_all)
|
|
107
|
+
|
|
108
|
+
async def add(self, obj: T) -> T:
|
|
109
|
+
"""Adds an Row to the Database.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
obj (T): Object to add.
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
T: Added object.
|
|
116
|
+
"""
|
|
117
|
+
self._session.add(obj)
|
|
118
|
+
return obj
|
|
119
|
+
|
|
120
|
+
async def delete(self, obj: T) -> T:
|
|
121
|
+
"""Deletes a Row from the Database.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
obj (T): Object to delete.
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
T: Deleted object.
|
|
128
|
+
"""
|
|
129
|
+
await self._session.delete(obj)
|
|
130
|
+
return obj
|
|
131
|
+
|
|
132
|
+
async def exec(self, statement: Executable, *args, **kwargs) -> Result:
|
|
133
|
+
"""Executes a SQL Statement.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
statement (Executable): SQL statement.
|
|
137
|
+
*args (tuple): Positional arguments.
|
|
138
|
+
**kwargs (dict): Keyword arguments.
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
Result: A buffered Result object.
|
|
142
|
+
"""
|
|
143
|
+
return await self._session.execute(statement, *args, **kwargs)
|
|
144
|
+
|
|
145
|
+
async def stream(self, statement: Executable, *args, **kwargs) -> AsyncScalarResult:
|
|
146
|
+
"""Returns an Stream of Query Results.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
statement (Executable): SQL statement.
|
|
150
|
+
*args (tuple): Positional arguments.
|
|
151
|
+
**kwargs (dict): Keyword arguments.
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
AsyncScalarResult: An AsyncScalarResult filtering object.
|
|
155
|
+
"""
|
|
156
|
+
return (await self._session.stream(statement, *args, **kwargs)).scalars()
|
|
157
|
+
|
|
158
|
+
async def all(self, statement: Executable, *args, **kwargs) -> list[T]:
|
|
159
|
+
"""Returns all matches for a Query.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
statement (Executable): SQL statement.
|
|
163
|
+
*args (tuple): Positional arguments.
|
|
164
|
+
**kwargs (dict): Keyword arguments.
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
list[T]: List of rows.
|
|
168
|
+
"""
|
|
169
|
+
return [x async for x in await self.stream(statement, *args, **kwargs)]
|
|
170
|
+
|
|
171
|
+
async def first(self, *args, **kwargs) -> dict | None:
|
|
172
|
+
"""Returns first match for a Query.
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
*args (tuple): Positional arguments.
|
|
176
|
+
**kwargs (dict): Keyword arguments.
|
|
177
|
+
|
|
178
|
+
Returns:
|
|
179
|
+
dict | None: First match for the Query, or None if there is no match.
|
|
180
|
+
"""
|
|
181
|
+
return (await self.exec(*args, **kwargs)).scalar()
|
|
182
|
+
|
|
183
|
+
async def exists(self, *args, **kwargs) -> bool:
|
|
184
|
+
"""Checks if there is a match for this Query.
|
|
185
|
+
|
|
186
|
+
Args:
|
|
187
|
+
*args (tuple): Positional arguments.
|
|
188
|
+
**kwargs (dict): Keyword arguments.
|
|
189
|
+
|
|
190
|
+
Returns:
|
|
191
|
+
bool: Whether there is a match for this Query
|
|
192
|
+
"""
|
|
193
|
+
return await self.first(exists(*args, **kwargs).select())
|
|
194
|
+
|
|
195
|
+
async def count(self, *args, **kwargs) -> int:
|
|
196
|
+
"""Counts matches for a Query.
|
|
197
|
+
|
|
198
|
+
Args:
|
|
199
|
+
*args (tuple): Positional arguments.
|
|
200
|
+
**kwargs (dict): Keyword arguments.
|
|
201
|
+
|
|
202
|
+
Returns:
|
|
203
|
+
int: Number of matches for a Query
|
|
204
|
+
"""
|
|
205
|
+
return await self.first(select(count()).select_from(*args, **kwargs))
|
|
206
|
+
|
|
207
|
+
async def commit(self):
|
|
208
|
+
"""Commits/Saves changes to Database."""
|
|
209
|
+
await self._session.commit()
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
logger = get_logger(__name__)
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
class DatabaseDependency:
|
|
216
|
+
"""A class to represent a database dependency."""
|
|
217
|
+
|
|
218
|
+
db: DB
|
|
219
|
+
database_url_options: dict
|
|
220
|
+
engine_options: dict
|
|
221
|
+
initialized: bool = False
|
|
222
|
+
|
|
223
|
+
def __init__(self):
|
|
224
|
+
"""Initialize the class."""
|
|
225
|
+
self.database_url_options = {
|
|
226
|
+
"host": DBConfig.db_host,
|
|
227
|
+
"port": DBConfig.db_port,
|
|
228
|
+
"database": DBConfig.db_database,
|
|
229
|
+
"username": DBConfig.db_username,
|
|
230
|
+
"password": DBConfig.db_password,
|
|
231
|
+
}
|
|
232
|
+
self.database_url_options = {k: v for k, v in self.database_url_options.items() if v != ""}
|
|
233
|
+
self.engine_options: dict = {
|
|
234
|
+
# "pool_size": DB_POOL_SIZE, ## not used by sqlite
|
|
235
|
+
# "max_overflow": DB_MAX_OVERFLOW, ## not used by sqlite
|
|
236
|
+
"poolclass": DBConfig.db_pool,
|
|
237
|
+
}
|
|
238
|
+
self.engine_options = {k: int(v) for k, v in self.engine_options.items() if v != ""}
|
|
239
|
+
if self.engine_options["poolclass"] == 0:
|
|
240
|
+
self.engine_options["poolclass"] = NullPool
|
|
241
|
+
else:
|
|
242
|
+
del self.engine_options["poolclass"]
|
|
243
|
+
self.db = DB(DBConfig.db_driver, options=self.engine_options, **self.database_url_options)
|
|
244
|
+
logger.info("Connected to Database")
|
|
245
|
+
|
|
246
|
+
async def init(self) -> None:
|
|
247
|
+
"""Set whether the dependency is initialized or not."""
|
|
248
|
+
if self.initialized:
|
|
249
|
+
return
|
|
250
|
+
|
|
251
|
+
# logger.info("Create Tables")
|
|
252
|
+
self.initialized = True
|
|
253
|
+
# await self.db.create_tables()
|
|
254
|
+
|
|
255
|
+
async def __call__(self) -> DB:
|
|
256
|
+
"""Define __call__."""
|
|
257
|
+
if not self.initialized:
|
|
258
|
+
await self.init()
|
|
259
|
+
return self.db
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
database_dependency: DatabaseDependency = DatabaseDependency()
|
|
263
|
+
Base: DeclarativeMeta = database_dependency.db.Base
|
desdeo/api/utils/database.py
CHANGED
|
@@ -1,274 +1,36 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Utilities related to handling the database."""
|
|
2
2
|
|
|
3
|
-
from
|
|
4
|
-
from typing import TypeVar
|
|
3
|
+
from sqlmodel import Session
|
|
5
4
|
|
|
6
|
-
from
|
|
7
|
-
from sqlalchemy.ext.asyncio import (
|
|
8
|
-
AsyncEngine,
|
|
9
|
-
AsyncScalarResult,
|
|
10
|
-
AsyncSession,
|
|
11
|
-
create_async_engine,
|
|
12
|
-
)
|
|
13
|
-
from sqlalchemy.future import select as sa_select
|
|
14
|
-
from sqlalchemy.orm import DeclarativeMeta, declarative_base, sessionmaker
|
|
15
|
-
from sqlalchemy.pool import NullPool
|
|
16
|
-
from sqlalchemy.sql import Executable
|
|
17
|
-
from sqlalchemy.sql.expression import Delete
|
|
18
|
-
from sqlalchemy.sql.expression import delete as sa_delete
|
|
19
|
-
from sqlalchemy.sql.expression import exists as sa_exists
|
|
20
|
-
from sqlalchemy.sql.functions import count
|
|
21
|
-
from sqlalchemy.sql.selectable import Exists, Select
|
|
5
|
+
from desdeo.api.models import StateDB, UserSavedSolutionDB
|
|
22
6
|
|
|
23
|
-
from desdeo.api.config import SettingsConfig
|
|
24
7
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
# set development setting, e.g., DBConfig = DatabaseDeployConfig
|
|
33
|
-
pass
|
|
34
|
-
|
|
35
|
-
T = TypeVar("T")
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
def select(entity) -> Select:
|
|
39
|
-
"""Shortcut for :meth:`sqlalchemy.future.select`.
|
|
40
|
-
|
|
41
|
-
Args:
|
|
42
|
-
entity (sqlalchemy.sql.expression.FromClause): Entities / DB model to SELECT from.
|
|
43
|
-
|
|
44
|
-
Returns:
|
|
45
|
-
Select: A Select class object
|
|
46
|
-
"""
|
|
47
|
-
return sa_select(entity)
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
def filter_by(cls, *args, **kwargs) -> Select:
|
|
51
|
-
"""Shortcut for :meth:`sqlalchemy.future.Select.filter_by`.
|
|
52
|
-
|
|
53
|
-
Args:
|
|
54
|
-
cls (sqlalchemy.sql.expression.FromClause): Entities / DB model to SELECT from.
|
|
55
|
-
*args (tuple): Positional arguments.
|
|
56
|
-
**kwargs (dict): Keyword arguments.
|
|
57
|
-
|
|
58
|
-
Returns:
|
|
59
|
-
Select: A Select class object with a WHERE clause
|
|
60
|
-
"""
|
|
61
|
-
return select(cls, *args).filter_by(**kwargs)
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
def exists(*entities, **kwargs) -> Exists:
|
|
65
|
-
"""Shortcut for :meth:`sqlalchemy.sql.expression.exists`.
|
|
8
|
+
def user_save_solutions(
|
|
9
|
+
state_db: StateDB,
|
|
10
|
+
results: list,
|
|
11
|
+
user_id: int,
|
|
12
|
+
session: Session,
|
|
13
|
+
):
|
|
14
|
+
"""Save solutions to the user's archive and create new state in the database.
|
|
66
15
|
|
|
67
16
|
Args:
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
Exists: A new Exists construct
|
|
17
|
+
state_db: The state containing the solutions
|
|
18
|
+
results: List of solutions to save
|
|
19
|
+
user_id: ID of the user saving the solutions
|
|
20
|
+
session: Database session
|
|
73
21
|
"""
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
"""
|
|
86
|
-
return sa_delete(table)
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
class DB:
|
|
90
|
-
"""An async SQLAlchemy ORM wrapper."""
|
|
91
|
-
|
|
92
|
-
Base: DeclarativeMeta
|
|
93
|
-
_engine: AsyncEngine
|
|
94
|
-
_session: AsyncSession
|
|
95
|
-
|
|
96
|
-
def __init__(self, driver: str, options: dict | None = None, **kwargs):
|
|
97
|
-
"""Init the class.
|
|
98
|
-
|
|
99
|
-
Args:
|
|
100
|
-
driver (str): Drivername for db url.
|
|
101
|
-
options (dict, optional): Options for AsyncEngine instance.
|
|
102
|
-
**kwargs (dict): Keyword arguments.
|
|
103
|
-
"""
|
|
104
|
-
if options is None:
|
|
105
|
-
options = {"pool_size": 20, "max_overflow": 20}
|
|
106
|
-
|
|
107
|
-
url: str = URL.create(drivername=driver, **kwargs)
|
|
108
|
-
self._engine = create_async_engine(url, echo=True, pool_pre_ping=True, pool_recycle=300, **options)
|
|
109
|
-
self.Base = declarative_base()
|
|
110
|
-
self._session: AsyncSession = sessionmaker(self._engine, expire_on_commit=False, class_=AsyncSession)()
|
|
111
|
-
|
|
112
|
-
async def create_tables(self):
|
|
113
|
-
"""Creates all Model Tables."""
|
|
114
|
-
async with self._engine.begin() as conn:
|
|
115
|
-
await conn.run_sync(self.Base.metadata.create_all)
|
|
116
|
-
|
|
117
|
-
async def add(self, obj: T) -> T:
|
|
118
|
-
"""Adds an Row to the Database.
|
|
119
|
-
|
|
120
|
-
Args:
|
|
121
|
-
obj (T): Object to add.
|
|
122
|
-
|
|
123
|
-
Returns:
|
|
124
|
-
T: Added object.
|
|
125
|
-
"""
|
|
126
|
-
self._session.add(obj)
|
|
127
|
-
return obj
|
|
128
|
-
|
|
129
|
-
async def delete(self, obj: T) -> T:
|
|
130
|
-
"""Deletes a Row from the Database.
|
|
131
|
-
|
|
132
|
-
Args:
|
|
133
|
-
obj (T): Object to delete.
|
|
134
|
-
|
|
135
|
-
Returns:
|
|
136
|
-
T: Deleted object.
|
|
137
|
-
"""
|
|
138
|
-
await self._session.delete(obj)
|
|
139
|
-
return obj
|
|
140
|
-
|
|
141
|
-
async def exec(self, statement: Executable, *args, **kwargs) -> Result:
|
|
142
|
-
"""Executes a SQL Statement.
|
|
143
|
-
|
|
144
|
-
Args:
|
|
145
|
-
statement (Executable): SQL statement.
|
|
146
|
-
*args (tuple): Positional arguments.
|
|
147
|
-
**kwargs (dict): Keyword arguments.
|
|
148
|
-
|
|
149
|
-
Returns:
|
|
150
|
-
Result: A buffered Result object.
|
|
151
|
-
"""
|
|
152
|
-
return await self._session.execute(statement, *args, **kwargs)
|
|
153
|
-
|
|
154
|
-
async def stream(self, statement: Executable, *args, **kwargs) -> AsyncScalarResult:
|
|
155
|
-
"""Returns an Stream of Query Results.
|
|
156
|
-
|
|
157
|
-
Args:
|
|
158
|
-
statement (Executable): SQL statement.
|
|
159
|
-
*args (tuple): Positional arguments.
|
|
160
|
-
**kwargs (dict): Keyword arguments.
|
|
161
|
-
|
|
162
|
-
Returns:
|
|
163
|
-
AsyncScalarResult: An AsyncScalarResult filtering object.
|
|
164
|
-
"""
|
|
165
|
-
return (await self._session.stream(statement, *args, **kwargs)).scalars()
|
|
166
|
-
|
|
167
|
-
async def all(self, statement: Executable, *args, **kwargs) -> list[T]:
|
|
168
|
-
"""Returns all matches for a Query.
|
|
169
|
-
|
|
170
|
-
Args:
|
|
171
|
-
statement (Executable): SQL statement.
|
|
172
|
-
*args (tuple): Positional arguments.
|
|
173
|
-
**kwargs (dict): Keyword arguments.
|
|
174
|
-
|
|
175
|
-
Returns:
|
|
176
|
-
list[T]: List of rows.
|
|
177
|
-
"""
|
|
178
|
-
return [x async for x in await self.stream(statement, *args, **kwargs)]
|
|
179
|
-
|
|
180
|
-
async def first(self, *args, **kwargs) -> dict | None:
|
|
181
|
-
"""Returns first match for a Query.
|
|
182
|
-
|
|
183
|
-
Args:
|
|
184
|
-
*args (tuple): Positional arguments.
|
|
185
|
-
**kwargs (dict): Keyword arguments.
|
|
186
|
-
|
|
187
|
-
Returns:
|
|
188
|
-
dict | None: First match for the Query, or None if there is no match.
|
|
189
|
-
"""
|
|
190
|
-
return (await self.exec(*args, **kwargs)).scalar()
|
|
191
|
-
|
|
192
|
-
async def exists(self, *args, **kwargs) -> bool:
|
|
193
|
-
"""Checks if there is a match for this Query.
|
|
194
|
-
|
|
195
|
-
Args:
|
|
196
|
-
*args (tuple): Positional arguments.
|
|
197
|
-
**kwargs (dict): Keyword arguments.
|
|
198
|
-
|
|
199
|
-
Returns:
|
|
200
|
-
bool: Whether there is a match for this Query
|
|
201
|
-
"""
|
|
202
|
-
return await self.first(exists(*args, **kwargs).select())
|
|
203
|
-
|
|
204
|
-
async def count(self, *args, **kwargs) -> int:
|
|
205
|
-
"""Counts matches for a Query.
|
|
206
|
-
|
|
207
|
-
Args:
|
|
208
|
-
*args (tuple): Positional arguments.
|
|
209
|
-
**kwargs (dict): Keyword arguments.
|
|
210
|
-
|
|
211
|
-
Returns:
|
|
212
|
-
int: Number of matches for a Query
|
|
213
|
-
"""
|
|
214
|
-
return await self.first(select(count()).select_from(*args, **kwargs))
|
|
215
|
-
|
|
216
|
-
async def commit(self):
|
|
217
|
-
"""Commits/Saves changes to Database."""
|
|
218
|
-
await self._session.commit()
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
logger = get_logger(__name__)
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
class DatabaseDependency:
|
|
225
|
-
"""A class to represent a database dependency."""
|
|
226
|
-
|
|
227
|
-
db: DB
|
|
228
|
-
database_url_options: dict
|
|
229
|
-
engine_options: dict
|
|
230
|
-
initialized: bool = False
|
|
231
|
-
|
|
232
|
-
def __init__(self):
|
|
233
|
-
"""Initialize the class."""
|
|
234
|
-
self.database_url_options = {
|
|
235
|
-
"host": DBConfig.db_host,
|
|
236
|
-
"port": DBConfig.db_port,
|
|
237
|
-
"database": DBConfig.db_database,
|
|
238
|
-
"username": DBConfig.db_username,
|
|
239
|
-
"password": DBConfig.db_password,
|
|
240
|
-
}
|
|
241
|
-
self.database_url_options = {k: v for k, v in self.database_url_options.items() if v != ""}
|
|
242
|
-
self.engine_options: dict = {
|
|
243
|
-
# "pool_size": DB_POOL_SIZE, ## not used by sqlite
|
|
244
|
-
# "max_overflow": DB_MAX_OVERFLOW, ## not used by sqlite
|
|
245
|
-
"poolclass": DBConfig.db_pool,
|
|
246
|
-
}
|
|
247
|
-
self.engine_options = {k: int(v) for k, v in self.engine_options.items() if v != ""}
|
|
248
|
-
if self.engine_options["poolclass"] == 0:
|
|
249
|
-
self.engine_options["poolclass"] = NullPool
|
|
250
|
-
else:
|
|
251
|
-
del self.engine_options["poolclass"]
|
|
252
|
-
self.db = DB(
|
|
253
|
-
getenv("DB_DRIVER", "postgresql+asyncpg"), options=self.engine_options, **self.database_url_options
|
|
22
|
+
# Create archive entries for selected solutions
|
|
23
|
+
for solution in results:
|
|
24
|
+
archive_entry = UserSavedSolutionDB(
|
|
25
|
+
name=solution.name if solution.name else None,
|
|
26
|
+
objective_values=solution.objective_values,
|
|
27
|
+
address_state=solution.address_state,
|
|
28
|
+
state_id=state_db.id,
|
|
29
|
+
address_result=solution.address_result,
|
|
30
|
+
user_id=user_id,
|
|
31
|
+
problem_id=state_db.problem_id,
|
|
32
|
+
state=state_db,
|
|
254
33
|
)
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
"""Set whether the dependency is initialized or not."""
|
|
259
|
-
if self.initialized:
|
|
260
|
-
return
|
|
261
|
-
|
|
262
|
-
# logger.info("Create Tables")
|
|
263
|
-
self.initialized = True
|
|
264
|
-
# await self.db.create_tables()
|
|
265
|
-
|
|
266
|
-
async def __call__(self) -> DB:
|
|
267
|
-
"""Define __call__."""
|
|
268
|
-
if not self.initialized:
|
|
269
|
-
await self.init()
|
|
270
|
-
return self.db
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
database_dependency: DatabaseDependency = DatabaseDependency()
|
|
274
|
-
Base: DeclarativeMeta = database_dependency.db.Base
|
|
34
|
+
session.add(archive_entry)
|
|
35
|
+
# state is already set in UserSavedSolutionDB, so no need to add it explictly
|
|
36
|
+
session.commit()
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""Utility functions for EMO database operations."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def _convert_dataframe_to_dict_list(df):
|
|
5
|
+
"""Convert DataFrame to list of dictionaries, handling different DataFrame types."""
|
|
6
|
+
if df is None:
|
|
7
|
+
return []
|
|
8
|
+
|
|
9
|
+
# Check if it's a pandas DataFrame
|
|
10
|
+
if hasattr(df, "iterrows"):
|
|
11
|
+
return [row.to_dict() for _, row in df.iterrows()]
|
|
12
|
+
|
|
13
|
+
# Check if it's a Polars DataFrame
|
|
14
|
+
elif hasattr(df, "iter_rows"):
|
|
15
|
+
# Get column names
|
|
16
|
+
columns = df.columns
|
|
17
|
+
return [dict(zip(columns, row)) for row in df.iter_rows()]
|
|
18
|
+
|
|
19
|
+
# Check if it's already a list of dictionaries
|
|
20
|
+
elif isinstance(df, list):
|
|
21
|
+
return df
|
|
22
|
+
|
|
23
|
+
# Check if it has to_dict method (pandas Series)
|
|
24
|
+
elif hasattr(df, "to_dict"):
|
|
25
|
+
return [df.to_dict()]
|
|
26
|
+
|
|
27
|
+
# Try to convert to dict if it's a single row
|
|
28
|
+
elif hasattr(df, "__iter__") and not isinstance(df, (str, dict)):
|
|
29
|
+
try:
|
|
30
|
+
# Assume it's iterable with column names
|
|
31
|
+
if hasattr(df, "columns"):
|
|
32
|
+
return [dict(zip(df.columns, row)) for row in df]
|
|
33
|
+
else:
|
|
34
|
+
# Generic conversion
|
|
35
|
+
return [dict(enumerate(row)) for row in df]
|
|
36
|
+
except Exception:
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
# If all else fails, try to convert to string representation
|
|
40
|
+
return [{"data": str(df)}]
|
desdeo/core.py
CHANGED
|
@@ -2,10 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
import shutil
|
|
4
4
|
import warnings
|
|
5
|
+
from os import getenv
|
|
6
|
+
|
|
7
|
+
from dotenv import load_dotenv
|
|
5
8
|
|
|
6
9
|
|
|
7
10
|
def _check_executables(executable_list):
|
|
8
11
|
missing_executables = []
|
|
12
|
+
load_dotenv()
|
|
13
|
+
if getenv("CHECK_EXECUTABLES", "true").lower() == "false":
|
|
14
|
+
return
|
|
15
|
+
|
|
9
16
|
for executable in executable_list:
|
|
10
17
|
if shutil.which(executable) is None:
|
|
11
18
|
missing_executables.append(executable)
|