desdeo 2.0.0__py3-none-any.whl → 2.1.1__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.
Files changed (130) hide show
  1. desdeo/adm/ADMAfsar.py +551 -0
  2. desdeo/adm/ADMChen.py +414 -0
  3. desdeo/adm/BaseADM.py +119 -0
  4. desdeo/adm/__init__.py +11 -0
  5. desdeo/api/__init__.py +6 -6
  6. desdeo/api/app.py +38 -28
  7. desdeo/api/config.py +65 -44
  8. desdeo/api/config.toml +23 -12
  9. desdeo/api/db.py +10 -8
  10. desdeo/api/db_init.py +12 -6
  11. desdeo/api/models/__init__.py +220 -20
  12. desdeo/api/models/archive.py +16 -27
  13. desdeo/api/models/emo.py +128 -0
  14. desdeo/api/models/enautilus.py +69 -0
  15. desdeo/api/models/gdm/gdm_aggregate.py +139 -0
  16. desdeo/api/models/gdm/gdm_base.py +69 -0
  17. desdeo/api/models/gdm/gdm_score_bands.py +114 -0
  18. desdeo/api/models/gdm/gnimbus.py +138 -0
  19. desdeo/api/models/generic.py +104 -0
  20. desdeo/api/models/generic_states.py +401 -0
  21. desdeo/api/models/nimbus.py +158 -0
  22. desdeo/api/models/preference.py +44 -6
  23. desdeo/api/models/problem.py +274 -64
  24. desdeo/api/models/session.py +4 -1
  25. desdeo/api/models/state.py +419 -52
  26. desdeo/api/models/user.py +7 -6
  27. desdeo/api/models/utopia.py +25 -0
  28. desdeo/api/routers/_EMO.backup +309 -0
  29. desdeo/api/routers/_NIMBUS.py +6 -3
  30. desdeo/api/routers/emo.py +497 -0
  31. desdeo/api/routers/enautilus.py +237 -0
  32. desdeo/api/routers/gdm/gdm_aggregate.py +234 -0
  33. desdeo/api/routers/gdm/gdm_base.py +420 -0
  34. desdeo/api/routers/gdm/gdm_score_bands/gdm_score_bands_manager.py +398 -0
  35. desdeo/api/routers/gdm/gdm_score_bands/gdm_score_bands_routers.py +377 -0
  36. desdeo/api/routers/gdm/gnimbus/gnimbus_manager.py +698 -0
  37. desdeo/api/routers/gdm/gnimbus/gnimbus_routers.py +591 -0
  38. desdeo/api/routers/generic.py +233 -0
  39. desdeo/api/routers/nimbus.py +705 -0
  40. desdeo/api/routers/problem.py +201 -4
  41. desdeo/api/routers/reference_point_method.py +20 -44
  42. desdeo/api/routers/session.py +50 -26
  43. desdeo/api/routers/user_authentication.py +180 -26
  44. desdeo/api/routers/utils.py +187 -0
  45. desdeo/api/routers/utopia.py +230 -0
  46. desdeo/api/schema.py +10 -4
  47. desdeo/api/tests/conftest.py +94 -2
  48. desdeo/api/tests/test_enautilus.py +330 -0
  49. desdeo/api/tests/test_models.py +550 -72
  50. desdeo/api/tests/test_routes.py +902 -43
  51. desdeo/api/utils/_database.py +263 -0
  52. desdeo/api/utils/database.py +28 -266
  53. desdeo/api/utils/emo_database.py +40 -0
  54. desdeo/core.py +7 -0
  55. desdeo/emo/__init__.py +154 -24
  56. desdeo/emo/hooks/archivers.py +18 -2
  57. desdeo/emo/methods/EAs.py +128 -5
  58. desdeo/emo/methods/bases.py +9 -56
  59. desdeo/emo/methods/templates.py +111 -0
  60. desdeo/emo/operators/crossover.py +544 -42
  61. desdeo/emo/operators/evaluator.py +10 -14
  62. desdeo/emo/operators/generator.py +127 -24
  63. desdeo/emo/operators/mutation.py +212 -41
  64. desdeo/emo/operators/scalar_selection.py +202 -0
  65. desdeo/emo/operators/selection.py +956 -214
  66. desdeo/emo/operators/termination.py +124 -16
  67. desdeo/emo/options/__init__.py +108 -0
  68. desdeo/emo/options/algorithms.py +435 -0
  69. desdeo/emo/options/crossover.py +164 -0
  70. desdeo/emo/options/generator.py +131 -0
  71. desdeo/emo/options/mutation.py +260 -0
  72. desdeo/emo/options/repair.py +61 -0
  73. desdeo/emo/options/scalar_selection.py +66 -0
  74. desdeo/emo/options/selection.py +127 -0
  75. desdeo/emo/options/templates.py +383 -0
  76. desdeo/emo/options/termination.py +143 -0
  77. desdeo/gdm/__init__.py +22 -0
  78. desdeo/gdm/gdmtools.py +45 -0
  79. desdeo/gdm/score_bands.py +114 -0
  80. desdeo/gdm/voting_rules.py +50 -0
  81. desdeo/mcdm/__init__.py +23 -1
  82. desdeo/mcdm/enautilus.py +338 -0
  83. desdeo/mcdm/gnimbus.py +484 -0
  84. desdeo/mcdm/nautilus_navigator.py +7 -6
  85. desdeo/mcdm/reference_point_method.py +70 -0
  86. desdeo/problem/__init__.py +16 -11
  87. desdeo/problem/evaluator.py +4 -5
  88. desdeo/problem/external/__init__.py +18 -0
  89. desdeo/problem/external/core.py +356 -0
  90. desdeo/problem/external/pymoo_provider.py +266 -0
  91. desdeo/problem/external/runtime.py +44 -0
  92. desdeo/problem/gurobipy_evaluator.py +37 -12
  93. desdeo/problem/infix_parser.py +1 -16
  94. desdeo/problem/json_parser.py +7 -11
  95. desdeo/problem/pyomo_evaluator.py +25 -6
  96. desdeo/problem/schema.py +73 -55
  97. desdeo/problem/simulator_evaluator.py +65 -15
  98. desdeo/problem/testproblems/__init__.py +26 -11
  99. desdeo/problem/testproblems/benchmarks_server.py +120 -0
  100. desdeo/problem/testproblems/cake_problem.py +185 -0
  101. desdeo/problem/testproblems/dmitry_forest_problem_discrete.py +71 -0
  102. desdeo/problem/testproblems/forest_problem.py +77 -69
  103. desdeo/problem/testproblems/multi_valued_constraints.py +119 -0
  104. desdeo/problem/testproblems/{river_pollution_problem.py → river_pollution_problems.py} +28 -22
  105. desdeo/problem/testproblems/single_objective.py +289 -0
  106. desdeo/problem/testproblems/zdt_problem.py +4 -1
  107. desdeo/problem/utils.py +1 -1
  108. desdeo/tools/__init__.py +39 -21
  109. desdeo/tools/desc_gen.py +22 -0
  110. desdeo/tools/generics.py +22 -2
  111. desdeo/tools/group_scalarization.py +3090 -0
  112. desdeo/tools/indicators_binary.py +107 -1
  113. desdeo/tools/indicators_unary.py +3 -16
  114. desdeo/tools/message.py +33 -2
  115. desdeo/tools/non_dominated_sorting.py +4 -3
  116. desdeo/tools/patterns.py +9 -7
  117. desdeo/tools/pyomo_solver_interfaces.py +49 -36
  118. desdeo/tools/reference_vectors.py +118 -351
  119. desdeo/tools/scalarization.py +340 -1413
  120. desdeo/tools/score_bands.py +491 -328
  121. desdeo/tools/utils.py +117 -49
  122. desdeo/tools/visualizations.py +67 -0
  123. desdeo/utopia_stuff/utopia_problem.py +1 -1
  124. desdeo/utopia_stuff/utopia_problem_old.py +1 -1
  125. {desdeo-2.0.0.dist-info → desdeo-2.1.1.dist-info}/METADATA +47 -30
  126. desdeo-2.1.1.dist-info/RECORD +180 -0
  127. {desdeo-2.0.0.dist-info → desdeo-2.1.1.dist-info}/WHEEL +1 -1
  128. desdeo-2.0.0.dist-info/RECORD +0 -120
  129. /desdeo/api/utils/{logger.py → _logger.py} +0 -0
  130. {desdeo-2.0.0.dist-info → desdeo-2.1.1.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
@@ -1,274 +1,36 @@
1
- """Database utils for the API."""
1
+ """Utilities related to handling the database."""
2
2
 
3
- from os import getenv
4
- from typing import TypeVar
3
+ from sqlmodel import Session
5
4
 
6
- from sqlalchemy.engine import URL, Result
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
- from .logger import get_logger
26
-
27
- if SettingsConfig.debug:
28
- from desdeo.api.config import DatabaseDebugConfig
29
-
30
- DBConfig = DatabaseDebugConfig
31
- else:
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
- *entities (tuple): Positional arguments.
69
- **kwargs (dict): Keyword arguments.
70
-
71
- Returns:
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
- return sa_exists(*entities, **kwargs)
75
-
76
-
77
- def delete(table) -> Delete:
78
- """Shortcut for :meth:`sqlalchemy.sql.expression.delete`.
79
-
80
- Args:
81
- table (sqlalchemy.sql.expression.FromClause): Entities / DB model.
82
-
83
- Returns:
84
- Delete: A Delete object
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
- logger.info("Connected to Database")
256
-
257
- async def init(self) -> None:
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)