gamsapi 52.5.0__cp312-cp312-win_amd64.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 (257) hide show
  1. gams/__init__.py +27 -0
  2. gams/_version.py +1 -0
  3. gams/connect/__init__.py +28 -0
  4. gams/connect/agents/__init__.py +24 -0
  5. gams/connect/agents/_excel/__init__.py +32 -0
  6. gams/connect/agents/_excel/excelagent.py +312 -0
  7. gams/connect/agents/_excel/workbook.py +155 -0
  8. gams/connect/agents/_sqlconnectors/__init__.py +42 -0
  9. gams/connect/agents/_sqlconnectors/_accesshandler.py +211 -0
  10. gams/connect/agents/_sqlconnectors/_databasehandler.py +250 -0
  11. gams/connect/agents/_sqlconnectors/_mysqlhandler.py +168 -0
  12. gams/connect/agents/_sqlconnectors/_postgreshandler.py +131 -0
  13. gams/connect/agents/_sqlconnectors/_pyodbchandler.py +112 -0
  14. gams/connect/agents/_sqlconnectors/_sqlalchemyhandler.py +74 -0
  15. gams/connect/agents/_sqlconnectors/_sqlitehandler.py +262 -0
  16. gams/connect/agents/_sqlconnectors/_sqlserverhandler.py +179 -0
  17. gams/connect/agents/concatenate.py +440 -0
  18. gams/connect/agents/connectagent.py +743 -0
  19. gams/connect/agents/csvreader.py +675 -0
  20. gams/connect/agents/csvwriter.py +151 -0
  21. gams/connect/agents/domainwriter.py +143 -0
  22. gams/connect/agents/excelreader.py +756 -0
  23. gams/connect/agents/excelwriter.py +467 -0
  24. gams/connect/agents/filter.py +223 -0
  25. gams/connect/agents/gamsreader.py +112 -0
  26. gams/connect/agents/gamswriter.py +239 -0
  27. gams/connect/agents/gdxreader.py +109 -0
  28. gams/connect/agents/gdxwriter.py +146 -0
  29. gams/connect/agents/labelmanipulator.py +303 -0
  30. gams/connect/agents/projection.py +539 -0
  31. gams/connect/agents/pythoncode.py +71 -0
  32. gams/connect/agents/rawcsvreader.py +248 -0
  33. gams/connect/agents/rawexcelreader.py +312 -0
  34. gams/connect/agents/schema/CSVReader.yaml +92 -0
  35. gams/connect/agents/schema/CSVWriter.yaml +44 -0
  36. gams/connect/agents/schema/Concatenate.yaml +52 -0
  37. gams/connect/agents/schema/DomainWriter.yaml +25 -0
  38. gams/connect/agents/schema/ExcelReader.yaml +121 -0
  39. gams/connect/agents/schema/ExcelWriter.yaml +78 -0
  40. gams/connect/agents/schema/Filter.yaml +74 -0
  41. gams/connect/agents/schema/GAMSReader.yaml +20 -0
  42. gams/connect/agents/schema/GAMSWriter.yaml +47 -0
  43. gams/connect/agents/schema/GDXReader.yaml +23 -0
  44. gams/connect/agents/schema/GDXWriter.yaml +32 -0
  45. gams/connect/agents/schema/LabelManipulator.yaml +99 -0
  46. gams/connect/agents/schema/Projection.yaml +24 -0
  47. gams/connect/agents/schema/PythonCode.yaml +6 -0
  48. gams/connect/agents/schema/RawCSVReader.yaml +34 -0
  49. gams/connect/agents/schema/RawExcelReader.yaml +42 -0
  50. gams/connect/agents/schema/SQLReader.yaml +75 -0
  51. gams/connect/agents/schema/SQLWriter.yaml +103 -0
  52. gams/connect/agents/sqlreader.py +301 -0
  53. gams/connect/agents/sqlwriter.py +276 -0
  54. gams/connect/connectdatabase.py +275 -0
  55. gams/connect/connectvalidator.py +93 -0
  56. gams/connect/errors.py +34 -0
  57. gams/control/__init__.py +136 -0
  58. gams/control/database.py +2231 -0
  59. gams/control/execution.py +1900 -0
  60. gams/control/options.py +2792 -0
  61. gams/control/workspace.py +1198 -0
  62. gams/core/__init__.py +24 -0
  63. gams/core/cfg/__init__.py +26 -0
  64. gams/core/cfg/_cfgmcc.cp312-win_amd64.pyd +0 -0
  65. gams/core/cfg/cfgmcc.py +519 -0
  66. gams/core/dct/__init__.py +26 -0
  67. gams/core/dct/_dctmcc.cp312-win_amd64.pyd +0 -0
  68. gams/core/dct/dctmcc.py +574 -0
  69. gams/core/embedded/__init__.py +26 -0
  70. gams/core/embedded/gamsemb.py +1024 -0
  71. gams/core/emp/__init__.py +24 -0
  72. gams/core/emp/emplexer.py +89 -0
  73. gams/core/emp/empyacc.py +281 -0
  74. gams/core/gdx/__init__.py +26 -0
  75. gams/core/gdx/_gdxcc.cp312-win_amd64.pyd +0 -0
  76. gams/core/gdx/gdxcc.py +866 -0
  77. gams/core/gev/__init__.py +26 -0
  78. gams/core/gev/_gevmcc.cp312-win_amd64.pyd +0 -0
  79. gams/core/gev/gevmcc.py +855 -0
  80. gams/core/gmd/__init__.py +26 -0
  81. gams/core/gmd/_gmdcc.cp312-win_amd64.pyd +0 -0
  82. gams/core/gmd/gmdcc.py +917 -0
  83. gams/core/gmo/__init__.py +26 -0
  84. gams/core/gmo/_gmomcc.cp312-win_amd64.pyd +0 -0
  85. gams/core/gmo/gmomcc.py +2046 -0
  86. gams/core/idx/__init__.py +26 -0
  87. gams/core/idx/_idxcc.cp312-win_amd64.pyd +0 -0
  88. gams/core/idx/idxcc.py +510 -0
  89. gams/core/numpy/__init__.py +29 -0
  90. gams/core/numpy/_gams2numpy.cp312-win_amd64.pyd +0 -0
  91. gams/core/numpy/gams2numpy.py +1048 -0
  92. gams/core/opt/__init__.py +26 -0
  93. gams/core/opt/_optcc.cp312-win_amd64.pyd +0 -0
  94. gams/core/opt/optcc.py +840 -0
  95. gams/engine/__init__.py +204 -0
  96. gams/engine/api/__init__.py +13 -0
  97. gams/engine/api/auth_api.py +7653 -0
  98. gams/engine/api/cleanup_api.py +751 -0
  99. gams/engine/api/default_api.py +887 -0
  100. gams/engine/api/hypercube_api.py +2629 -0
  101. gams/engine/api/jobs_api.py +5229 -0
  102. gams/engine/api/licenses_api.py +2220 -0
  103. gams/engine/api/namespaces_api.py +7783 -0
  104. gams/engine/api/usage_api.py +5627 -0
  105. gams/engine/api/users_api.py +5931 -0
  106. gams/engine/api_client.py +804 -0
  107. gams/engine/api_response.py +21 -0
  108. gams/engine/configuration.py +601 -0
  109. gams/engine/exceptions.py +216 -0
  110. gams/engine/models/__init__.py +86 -0
  111. gams/engine/models/bad_input.py +89 -0
  112. gams/engine/models/cleanable_job_result.py +104 -0
  113. gams/engine/models/cleanable_job_result_page.py +113 -0
  114. gams/engine/models/engine_license.py +107 -0
  115. gams/engine/models/files_not_found.py +93 -0
  116. gams/engine/models/forwarded_token_response.py +112 -0
  117. gams/engine/models/generic_key_value_pair.py +89 -0
  118. gams/engine/models/hypercube.py +160 -0
  119. gams/engine/models/hypercube_page.py +111 -0
  120. gams/engine/models/hypercube_summary.py +91 -0
  121. gams/engine/models/hypercube_token.py +97 -0
  122. gams/engine/models/identity_provider.py +107 -0
  123. gams/engine/models/identity_provider_ldap.py +121 -0
  124. gams/engine/models/identity_provider_oauth2.py +146 -0
  125. gams/engine/models/identity_provider_oauth2_scope.py +89 -0
  126. gams/engine/models/identity_provider_oauth2_with_secret.py +152 -0
  127. gams/engine/models/identity_provider_oidc.py +133 -0
  128. gams/engine/models/identity_provider_oidc_with_secret.py +143 -0
  129. gams/engine/models/inex.py +91 -0
  130. gams/engine/models/invitation.py +136 -0
  131. gams/engine/models/invitation_quota.py +106 -0
  132. gams/engine/models/invitation_token.py +87 -0
  133. gams/engine/models/job.py +165 -0
  134. gams/engine/models/job_no_text_entry.py +138 -0
  135. gams/engine/models/job_no_text_entry_page.py +111 -0
  136. gams/engine/models/license.py +91 -0
  137. gams/engine/models/log_piece.py +96 -0
  138. gams/engine/models/message.py +87 -0
  139. gams/engine/models/message_and_token.py +99 -0
  140. gams/engine/models/message_with_webhook_id.py +89 -0
  141. gams/engine/models/model_auth_token.py +87 -0
  142. gams/engine/models/model_configuration.py +125 -0
  143. gams/engine/models/model_default_instance.py +99 -0
  144. gams/engine/models/model_default_user_instance.py +98 -0
  145. gams/engine/models/model_hypercube_job.py +106 -0
  146. gams/engine/models/model_hypercube_usage.py +130 -0
  147. gams/engine/models/model_instance_info.py +116 -0
  148. gams/engine/models/model_instance_info_full.py +123 -0
  149. gams/engine/models/model_instance_pool_info.py +112 -0
  150. gams/engine/models/model_job_labels.py +179 -0
  151. gams/engine/models/model_job_usage.py +133 -0
  152. gams/engine/models/model_pool_usage.py +124 -0
  153. gams/engine/models/model_usage.py +115 -0
  154. gams/engine/models/model_user.py +96 -0
  155. gams/engine/models/model_userinstance_info.py +119 -0
  156. gams/engine/models/model_userinstancepool_info.py +95 -0
  157. gams/engine/models/model_version.py +91 -0
  158. gams/engine/models/models.py +120 -0
  159. gams/engine/models/namespace.py +104 -0
  160. gams/engine/models/namespace_quota.py +96 -0
  161. gams/engine/models/namespace_with_permission.py +96 -0
  162. gams/engine/models/not_found.py +91 -0
  163. gams/engine/models/password_policy.py +97 -0
  164. gams/engine/models/perm_and_username.py +89 -0
  165. gams/engine/models/quota.py +117 -0
  166. gams/engine/models/quota_exceeded.py +97 -0
  167. gams/engine/models/status_code_meaning.py +89 -0
  168. gams/engine/models/stream_entry.py +89 -0
  169. gams/engine/models/system_wide_license.py +92 -0
  170. gams/engine/models/text_entries.py +87 -0
  171. gams/engine/models/text_entry.py +101 -0
  172. gams/engine/models/time_span.py +95 -0
  173. gams/engine/models/time_span_pool_worker.py +99 -0
  174. gams/engine/models/token_forward_error.py +87 -0
  175. gams/engine/models/user.py +127 -0
  176. gams/engine/models/user_group_member.py +96 -0
  177. gams/engine/models/user_groups.py +108 -0
  178. gams/engine/models/vapid_info.py +87 -0
  179. gams/engine/models/webhook.py +138 -0
  180. gams/engine/models/webhook_parameterized_event.py +99 -0
  181. gams/engine/py.typed +0 -0
  182. gams/engine/rest.py +258 -0
  183. gams/magic/__init__.py +32 -0
  184. gams/magic/gams_magic.py +142 -0
  185. gams/magic/interactive.py +402 -0
  186. gams/tools/__init__.py +30 -0
  187. gams/tools/errors.py +34 -0
  188. gams/tools/toolcollection/__init__.py +24 -0
  189. gams/tools/toolcollection/alg/__init__.py +24 -0
  190. gams/tools/toolcollection/alg/rank.py +51 -0
  191. gams/tools/toolcollection/data/__init__.py +24 -0
  192. gams/tools/toolcollection/data/csvread.py +444 -0
  193. gams/tools/toolcollection/data/csvwrite.py +311 -0
  194. gams/tools/toolcollection/data/exceldump.py +47 -0
  195. gams/tools/toolcollection/data/sqlitewrite.py +276 -0
  196. gams/tools/toolcollection/gdxservice/__init__.py +24 -0
  197. gams/tools/toolcollection/gdxservice/gdxencoding.py +104 -0
  198. gams/tools/toolcollection/gdxservice/gdxrename.py +94 -0
  199. gams/tools/toolcollection/linalg/__init__.py +24 -0
  200. gams/tools/toolcollection/linalg/cholesky.py +57 -0
  201. gams/tools/toolcollection/linalg/eigenvalue.py +56 -0
  202. gams/tools/toolcollection/linalg/eigenvector.py +58 -0
  203. gams/tools/toolcollection/linalg/invert.py +55 -0
  204. gams/tools/toolcollection/linalg/ols.py +138 -0
  205. gams/tools/toolcollection/tooltemplate.py +321 -0
  206. gams/tools/toolcollection/win32/__init__.py +24 -0
  207. gams/tools/toolcollection/win32/excelmerge.py +93 -0
  208. gams/tools/toolcollection/win32/exceltalk.py +76 -0
  209. gams/tools/toolcollection/win32/msappavail.py +49 -0
  210. gams/tools/toolcollection/win32/shellexecute.py +54 -0
  211. gams/tools/tools.py +116 -0
  212. gams/transfer/__init__.py +35 -0
  213. gams/transfer/_abcs/__init__.py +37 -0
  214. gams/transfer/_abcs/container_abcs.py +433 -0
  215. gams/transfer/_internals/__init__.py +63 -0
  216. gams/transfer/_internals/algorithms.py +436 -0
  217. gams/transfer/_internals/casepreservingdict.py +124 -0
  218. gams/transfer/_internals/constants.py +270 -0
  219. gams/transfer/_internals/domainviolation.py +103 -0
  220. gams/transfer/_internals/specialvalues.py +172 -0
  221. gams/transfer/containers/__init__.py +26 -0
  222. gams/transfer/containers/_container.py +1794 -0
  223. gams/transfer/containers/_io/__init__.py +28 -0
  224. gams/transfer/containers/_io/containers.py +164 -0
  225. gams/transfer/containers/_io/gdx.py +1029 -0
  226. gams/transfer/containers/_io/gmd.py +872 -0
  227. gams/transfer/containers/_mixins/__init__.py +26 -0
  228. gams/transfer/containers/_mixins/ccc.py +1274 -0
  229. gams/transfer/syms/__init__.py +33 -0
  230. gams/transfer/syms/_methods/__init__.py +24 -0
  231. gams/transfer/syms/_methods/tables.py +120 -0
  232. gams/transfer/syms/_methods/toDict.py +115 -0
  233. gams/transfer/syms/_methods/toList.py +83 -0
  234. gams/transfer/syms/_methods/toValue.py +60 -0
  235. gams/transfer/syms/_mixins/__init__.py +32 -0
  236. gams/transfer/syms/_mixins/equals.py +626 -0
  237. gams/transfer/syms/_mixins/generateRecords.py +499 -0
  238. gams/transfer/syms/_mixins/pivot.py +313 -0
  239. gams/transfer/syms/_mixins/pve.py +627 -0
  240. gams/transfer/syms/_mixins/sa.py +27 -0
  241. gams/transfer/syms/_mixins/sapve.py +27 -0
  242. gams/transfer/syms/_mixins/saua.py +27 -0
  243. gams/transfer/syms/_mixins/sauapve.py +199 -0
  244. gams/transfer/syms/_mixins/spve.py +1528 -0
  245. gams/transfer/syms/_mixins/ve.py +936 -0
  246. gams/transfer/syms/container_syms/__init__.py +31 -0
  247. gams/transfer/syms/container_syms/_alias.py +984 -0
  248. gams/transfer/syms/container_syms/_equation.py +333 -0
  249. gams/transfer/syms/container_syms/_parameter.py +973 -0
  250. gams/transfer/syms/container_syms/_set.py +604 -0
  251. gams/transfer/syms/container_syms/_universe_alias.py +461 -0
  252. gams/transfer/syms/container_syms/_variable.py +321 -0
  253. gamsapi-52.5.0.dist-info/METADATA +150 -0
  254. gamsapi-52.5.0.dist-info/RECORD +257 -0
  255. gamsapi-52.5.0.dist-info/WHEEL +5 -0
  256. gamsapi-52.5.0.dist-info/licenses/LICENSE +22 -0
  257. gamsapi-52.5.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,131 @@
1
+ #
2
+ # GAMS - General Algebraic Modeling System Python API
3
+ #
4
+ # Copyright (c) 2017-2026 GAMS Development Corp. <support@gams.com>
5
+ # Copyright (c) 2017-2026 GAMS Software GmbH <support@gams.com>
6
+ #
7
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ # of this software and associated documentation files (the "Software"), to deal
9
+ # in the Software without restriction, including without limitation the rights
10
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ # copies of the Software, and to permit persons to whom the Software is
12
+ # furnished to do so, subject to the following conditions:
13
+ #
14
+ # The above copyright notice and this permission notice shall be included in all
15
+ # copies or substantial portions of the Software.
16
+ #
17
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
+ # SOFTWARE.
24
+ #
25
+
26
+ import pandas as pd
27
+ from gams.connect.agents._sqlconnectors._databasehandler import DatabaseConnector
28
+
29
+
30
+ class PostgresConnector(DatabaseConnector):
31
+ SUPPORTED_INSERT_METHODS = ["default", "bulkInsert"]
32
+
33
+ def connect(self, connection_details, connection_args, **kwargs) -> None:
34
+
35
+ import psycopg2 as sql
36
+
37
+ if kwargs.get("isWrite", True):
38
+ # autocommit is relevant only for SQLWriter
39
+ self._pg_autocommit = connection_args.pop("autocommit", False)
40
+
41
+ self._engine = sql.connect(**connection_details, **connection_args)
42
+ self._conn = self._engine.cursor()
43
+
44
+ def create_transaction(self):
45
+ self._engine.autocommit = self._pg_autocommit
46
+
47
+ def _check_table_exists(self, tableName: str, schema: str | None) -> bool:
48
+ tableExists = False
49
+
50
+ # striping escape characters is not required. SQLSServer is insensitive to use of escape character.
51
+ qualified_table_name = f"{schema}.{tableName}" if schema else tableName
52
+
53
+ query = f"""SELECT to_regclass('{qualified_table_name}')"""
54
+ self._conn.execute(query)
55
+ res = self._conn.fetchone()
56
+ # TODO: check for POSTGRES type(res)
57
+ ### res can be = (obj,) | None | (None,)
58
+ if isinstance(res, tuple):
59
+ if res[0]:
60
+ tableExists = True
61
+
62
+ return tableExists
63
+
64
+ def _create_table(
65
+ self,
66
+ df: pd.DataFrame,
67
+ tableName: str,
68
+ schema: str | None,
69
+ ifExists: str,
70
+ **kwargs,
71
+ ) -> None:
72
+ """
73
+ Drops an exisiting table and creates a new table with the same name. Uses specific SQL queries for each DBMS flavour.
74
+ """
75
+ tableCols = ""
76
+ for col, dtype in df.dtypes.items():
77
+ if dtype == "float64":
78
+ tableCols += f'"{col}" FLOAT,'
79
+ elif dtype == "int64":
80
+ tableCols += f'"{col}" BIGINT,'
81
+ elif dtype in ["object", "category"]:
82
+ tableCols += f'"{col}" TEXT,'
83
+
84
+ tableCols = tableCols[:-1]
85
+
86
+ if schema:
87
+ tableName = schema + "." + tableName
88
+
89
+ if ifExists == "replace":
90
+ try:
91
+ self._conn.execute(f"""DROP TABLE IF EXISTS {tableName};""")
92
+ except Exception as e:
93
+ self._raise_error(
94
+ f"Cannot drop table >{tableName}<.\nException from {type(e).__module__}: {type(e).__name__}> {e}"
95
+ )
96
+
97
+ self._conn.execute(f"""CREATE TABLE {tableName}({tableCols});""")
98
+ if self._traceValue > 1:
99
+ self._traceLog(
100
+ f"Created new table: >{tableName}< with columns: >{tableCols}<"
101
+ )
102
+
103
+ def _insert_data(self, df: pd.DataFrame, writeFunction_args: dict):
104
+ """
105
+ default: uses .execute_batch with page_size=100 (fixed and default)
106
+ bulkInsert: uses the .copy_expert method to stream a csv file into the DB
107
+ """
108
+ tableName = writeFunction_args["name"]
109
+ insertMethod = writeFunction_args["insertMethod"]
110
+
111
+ if writeFunction_args["schema"]:
112
+ tableName = writeFunction_args["schema"] + "." + tableName
113
+
114
+ if insertMethod == "bulkInsert":
115
+ import io
116
+
117
+ s_buf = io.StringIO()
118
+ df.to_csv(s_buf, index=False, header=False)
119
+ s_buf.seek(0)
120
+ colNames = ", ".join(f'"{ele}"' for ele in df.columns)
121
+ query = f"""COPY {tableName} ({colNames}) FROM STDIN WITH CSV"""
122
+ self._conn.copy_expert(query, file=s_buf)
123
+ elif insertMethod == "default":
124
+ from psycopg2.extras import execute_batch
125
+
126
+ placeHolder = "%s," * (len(df.columns) - 1)
127
+ query = f"INSERT INTO {tableName} VALUES(" + placeHolder + "%s)"
128
+ if df.isnull().values.any(): # replace NaN with None, for SQL NULL
129
+ df = df.astype(object).where(pd.notnull(df), None)
130
+ df_list = df.values.tolist()
131
+ execute_batch(self._conn, query, df_list)
@@ -0,0 +1,112 @@
1
+ #
2
+ # GAMS - General Algebraic Modeling System Python API
3
+ #
4
+ # Copyright (c) 2017-2026 GAMS Development Corp. <support@gams.com>
5
+ # Copyright (c) 2017-2026 GAMS Software GmbH <support@gams.com>
6
+ #
7
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ # of this software and associated documentation files (the "Software"), to deal
9
+ # in the Software without restriction, including without limitation the rights
10
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ # copies of the Software, and to permit persons to whom the Software is
12
+ # furnished to do so, subject to the following conditions:
13
+ #
14
+ # The above copyright notice and this permission notice shall be included in all
15
+ # copies or substantial portions of the Software.
16
+ #
17
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
+ # SOFTWARE.
24
+ #
25
+
26
+ import pandas as pd
27
+ from gams.connect.agents._sqlconnectors._databasehandler import DatabaseConnector
28
+
29
+
30
+ class PyodbcConnector(DatabaseConnector):
31
+ SUPPORTED_INSERT_METHODS = ["default"]
32
+ QUOTE_CHAR = ["[]", '""', "``"]
33
+
34
+ def connect(self, connection_details, connection_args, **kwargs) -> None:
35
+ import pyodbc as sql
36
+
37
+ self._engine = sql.connect(**connection_details, **connection_args)
38
+ self._conn = self._engine.cursor()
39
+
40
+ def create_transaction(self) -> None:
41
+ """Autocommit = False by default. One can change this directly by setting the option in the >connection< dictionary."""
42
+ pass
43
+
44
+ def _check_table_exists(self, tableName, schema):
45
+ tableExists = False
46
+ rawTableName = self._strip_escape_chars(
47
+ tableName=tableName, quote_chars=self.QUOTE_CHAR
48
+ )
49
+ for ele in self._conn.tables().fetchall():
50
+ if ele[2].lower() == rawTableName.lower():
51
+ tableExists = True
52
+
53
+ return tableExists
54
+
55
+ def _create_table(self, df, tableName, schema, ifExists, **kwargs):
56
+ """
57
+ Drops an exisiting table and creates a new table with the same name. Uses specific SQL queries for each DBMS flavour.
58
+ """
59
+ dtype_map: dict = kwargs["dtype_map"]
60
+ col_encloser: str = kwargs["columnEncloser"]
61
+ tableCols = ""
62
+ for col, dtype in df.dtypes.items():
63
+ new_col = (
64
+ f"{col_encloser[0]}{col}{col_encloser[1]}"
65
+ if len(col_encloser) > 1
66
+ else f"{col_encloser[0]}{col}{col_encloser[0]}"
67
+ )
68
+ if dtype == "float64":
69
+ tableCols += f"{new_col} {dtype_map.get('float', 'FLOAT')},"
70
+ elif dtype == "int64":
71
+ tableCols += f"{new_col} {dtype_map.get('integer', 'BIGINT')},"
72
+ elif dtype in ["object", "category"]:
73
+ tableCols += f"{new_col} {dtype_map.get('text', 'TEXT')},"
74
+
75
+ tableCols = tableCols[:-1]
76
+
77
+ if schema:
78
+ tableName = schema + "." + tableName
79
+
80
+ if ifExists == "replace":
81
+ try:
82
+ if self._check_table_exists(tableName, schema=None):
83
+ self._conn.execute(f"""DROP TABLE {tableName};""")
84
+ except Exception as e:
85
+ self._raise_error(
86
+ f"Cannot drop table >{tableName}<.\nException from {type(e).__module__}: {type(e).__name__}> {e}"
87
+ )
88
+
89
+ self._conn.execute(f"""CREATE TABLE {tableName}({tableCols});""")
90
+
91
+ if self._traceValue > 1:
92
+ self._traceLog(
93
+ f"Created new table: >{tableName}< with columns: >{tableCols}<"
94
+ )
95
+
96
+ def _insert_data(self, df: pd.DataFrame, writeFunction_args: dict) -> None:
97
+ placeHolder = "?," * (len(df.columns) - 1)
98
+ tableName = writeFunction_args["name"]
99
+ if df.isnull().values.any(): # replace NaN with None, for SQL NULL
100
+ df = df.astype(object).where(pd.notnull(df), None)
101
+ df_list = list(df.itertuples(index=False, name=None))
102
+ if writeFunction_args["schema"]:
103
+ tableName = writeFunction_args["schema"] + "." + tableName
104
+ query = f"INSERT INTO {tableName} VALUES(" + placeHolder + "?)"
105
+
106
+ if len(df_list) > 0:
107
+ self._conn.executemany(query, df_list)
108
+
109
+ elif self._traceValue > 1:
110
+ self._traceLog(
111
+ f"Empty symbol. No rows were inserted in table >{tableName}<."
112
+ )
@@ -0,0 +1,74 @@
1
+ #
2
+ # GAMS - General Algebraic Modeling System Python API
3
+ #
4
+ # Copyright (c) 2017-2026 GAMS Development Corp. <support@gams.com>
5
+ # Copyright (c) 2017-2026 GAMS Software GmbH <support@gams.com>
6
+ #
7
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ # of this software and associated documentation files (the "Software"), to deal
9
+ # in the Software without restriction, including without limitation the rights
10
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ # copies of the Software, and to permit persons to whom the Software is
12
+ # furnished to do so, subject to the following conditions:
13
+ #
14
+ # The above copyright notice and this permission notice shall be included in all
15
+ # copies or substantial portions of the Software.
16
+ #
17
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
+ # SOFTWARE.
24
+ #
25
+
26
+ import pandas as pd
27
+ from gams.connect.agents._sqlconnectors._databasehandler import DatabaseConnector
28
+
29
+
30
+ class SQLAlchemyConnector(DatabaseConnector):
31
+ SUPPORTED_INSERT_METHODS = ["default"]
32
+
33
+ def connect(self, connection_details, connection_args, **kwargs) -> None:
34
+ import sqlalchemy
35
+
36
+ con_str = sqlalchemy.engine.URL.create(**connection_details)
37
+ self._engine = sqlalchemy.create_engine(con_str, **connection_args)
38
+ self._conn = self._engine.connect()
39
+
40
+ def create_transaction(self) -> None:
41
+ self._conn.begin()
42
+
43
+ def read_table(self, sql_query: str, read_sql_args: dict) -> pd.DataFrame:
44
+ return pd.read_sql(sql=sql_query, con=self._conn, **read_sql_args)
45
+
46
+ def _execute_write(
47
+ self,
48
+ df: pd.DataFrame,
49
+ writeFunction_args: dict,
50
+ ):
51
+ to_sql_args = {
52
+ key: writeFunction_args[key]
53
+ for key in ["name", "schema", "if_exists", "index"]
54
+ }
55
+ to_sql_args.update(writeFunction_args["toSQLArguments"])
56
+ df.to_sql(con=self._conn, **to_sql_args)
57
+
58
+ def commit(self):
59
+ self._conn.commit()
60
+
61
+ def rollback(self):
62
+ self._conn.rollback()
63
+
64
+ def close(self):
65
+ self._engine.dispose()
66
+
67
+ def _check_table_exists(self, tableName, schema):
68
+ pass
69
+
70
+ def _create_table(self, df, tableName, schema, ifExists, **kwargs):
71
+ pass
72
+
73
+ def _insert_data(self, df, writeFunction_args):
74
+ pass
@@ -0,0 +1,262 @@
1
+ #
2
+ # GAMS - General Algebraic Modeling System Python API
3
+ #
4
+ # Copyright (c) 2017-2026 GAMS Development Corp. <support@gams.com>
5
+ # Copyright (c) 2017-2026 GAMS Software GmbH <support@gams.com>
6
+ #
7
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ # of this software and associated documentation files (the "Software"), to deal
9
+ # in the Software without restriction, including without limitation the rights
10
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ # copies of the Software, and to permit persons to whom the Software is
12
+ # furnished to do so, subject to the following conditions:
13
+ #
14
+ # The above copyright notice and this permission notice shall be included in all
15
+ # copies or substantial portions of the Software.
16
+ #
17
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
+ # SOFTWARE.
24
+ #
25
+
26
+ import pandas as pd
27
+ from gams import transfer as gt
28
+ from gams.connect.agents._sqlconnectors._databasehandler import DatabaseConnector
29
+
30
+
31
+ class SQLiteConnector(DatabaseConnector):
32
+ SUPPORTED_INSERT_METHODS = ["default"]
33
+ QUOTE_CHAR = ["[]", '""', "``"]
34
+
35
+ def connect(self, connection_details, connection_args, **kwargs) -> None:
36
+
37
+ import sqlite3 as sql
38
+
39
+ isWrite: bool = kwargs.get("isWrite", True)
40
+ self._globalCommit: bool = kwargs.get("_sqlitewrite_globalCommit", False)
41
+ self._smallFlag: bool = kwargs.get("small", False)
42
+ self._fastFlag: bool = kwargs.get("fast", False)
43
+
44
+ if isWrite:
45
+ from pathlib import Path
46
+
47
+ if self._smallFlag and Path(connection_details["database"]).is_file():
48
+ self._raise_error(
49
+ f"SQLite database file >{connection_details['database']}< already exists.\
50
+ Option small can only be enabled when writing a new SQLite database file."
51
+ )
52
+
53
+ self._engine = sql.connect(**connection_details, **connection_args)
54
+ self._conn = self._engine.cursor()
55
+
56
+ if self._fastFlag and isWrite:
57
+ self._special_pragma_control()
58
+
59
+ def _special_pragma_control(self):
60
+ ### Needs to be done before the beginning of a transaction
61
+ ### Tried the following PRAGMAs but little to no effect:
62
+ ### PRAGMA threads = 8; SQLite 3.15+
63
+ ### PRAGMA cache_size = -200000;
64
+ ### PRAGMA temp_store = MEMORY; for temporary objects
65
+ self._conn.execute("PRAGMA synchronous = OFF;")
66
+ self._conn.execute("PRAGMA journal_mode = OFF;")
67
+
68
+ def create_transaction(self) -> None:
69
+ self._conn.execute("BEGIN")
70
+
71
+ def _check_table_exists(self, tableName: str, schema: str | None) -> bool:
72
+ tableExists = False
73
+ rawTableName = self._strip_escape_chars(
74
+ tableName=tableName, quote_chars=self.QUOTE_CHAR
75
+ )
76
+ query = f"""SELECT name FROM sqlite_master WHERE type='table' AND name='{rawTableName}'"""
77
+ self._conn.execute(query)
78
+
79
+ res = self._conn.fetchone()
80
+
81
+ # TODO: check type(res) for sqlite
82
+ ### res can be = (obj,) | None | (None,)
83
+ if isinstance(res, tuple):
84
+ if res[0]:
85
+ tableExists = True
86
+
87
+ return tableExists
88
+
89
+ def _sqlite_create_uel_table(self, cc: gt.Container):
90
+ """
91
+ Fetches the UELs in use for each symbol and creates a mapping from each UEL to an integer.
92
+ The UELs are then renamed to their corresponding integer mappings.
93
+ This function is SQLite specific and is called only when small=True.
94
+
95
+ Parameter:
96
+ cc (gt.Container): The input gt.Container containing symbols and UELs to process.
97
+ """
98
+ uel_list = cc.getUELs(ignore_unused=True)
99
+ uel_list = pd.Series(
100
+ data=[str(i) for i in range(1, len(uel_list) + 1)], index=uel_list
101
+ )
102
+ cc.renameUELs(uel_list.to_dict(), allow_merge=True)
103
+ self._uel_table_df = uel_list.reset_index()
104
+ self._uel_table_df.columns = ["uni", "element_text"]
105
+
106
+ def _sqlite_create_view(self, viewName, dim, df_cols):
107
+ """
108
+ Creates user-friendly SQL views with the same name as the original table provided for the symbols.
109
+ If the column headers are part of the UEL table, they are mapped to their corresponding names in the views.
110
+ This function is SQLite specific and is called only when small=True.
111
+
112
+ Parameter:
113
+ viewName (str) : The original table name to be used as the name for the view.
114
+ dim (int) : Dimension of the symbol
115
+ df_cols (list) : Columns of the symbol
116
+ """
117
+ cols_to_match = df_cols[:dim]
118
+ numeric_cols = {ele: ele for ele in df_cols[dim:]}
119
+ match_headers = self._uel_table_df[
120
+ self._uel_table_df["element_text"].isin(numeric_cols.keys())
121
+ ]
122
+ match_headers = match_headers.set_index("element_text")["uni"].to_dict()
123
+ numeric_cols.update(match_headers)
124
+ tableName = f"[{viewName}$]"
125
+ select_query = ", ".join(
126
+ [f"UEL{i}.[uni] AS [{ele}]" for i, ele in enumerate(cols_to_match, 1)]
127
+ + [
128
+ f"{tableName}.[{key}] AS [{col_name}]"
129
+ for key, col_name in numeric_cols.items()
130
+ ]
131
+ )
132
+ join_query = "".join(
133
+ [
134
+ f"\nINNER JOIN [UEL$] AS UEL{i} ON {tableName}.[{ele}] = UEL{i}.[element_text]"
135
+ for i, ele in enumerate(cols_to_match, 1)
136
+ ]
137
+ )
138
+ final_query = f"CREATE VIEW [{viewName}] AS SELECT {select_query} FROM {tableName} {join_query};"
139
+ try:
140
+ self._conn.execute(f"DROP VIEW IF EXISTS [{viewName}];")
141
+ self._conn.execute(final_query)
142
+ except Exception as e:
143
+ self._raise_error(f"{e}")
144
+
145
+ def _create_table(
146
+ self,
147
+ df: pd.DataFrame,
148
+ tableName: str,
149
+ schema: str | None,
150
+ ifExists: str,
151
+ **kwargs,
152
+ ) -> None:
153
+ """
154
+ Drops an exisiting table and creates a new table with the same name. Uses specific SQL queries for each DBMS flavour.
155
+ """
156
+
157
+ if tableName == "[UEL$]":
158
+ self._conn.execute(
159
+ """CREATE TABLE [UEL$]( uni TEXT, element_text INTEGER PRIMARY KEY ASC);"""
160
+ )
161
+
162
+ if self._traceValue > 1:
163
+ self._traceLog(f"Created new table: >{tableName}<.")
164
+ else:
165
+ tableCols = ""
166
+ for col, dtype in df.dtypes.items():
167
+ if dtype == "float64":
168
+ tableCols += f"[{col}] FLOAT,"
169
+ elif dtype == "int64":
170
+ tableCols += f"[{col}] BIGINT,"
171
+ elif dtype in ["object", "category"]:
172
+ tableCols += f"[{col}] VARCHAR(255),"
173
+
174
+ tableCols = tableCols[:-1]
175
+
176
+ if schema:
177
+ tableName = schema + "." + tableName
178
+
179
+ if ifExists == "replace":
180
+ try:
181
+ self._conn.execute(f"""DROP TABLE IF EXISTS {tableName};""")
182
+ except Exception as e:
183
+ self._raise_error(
184
+ f"Cannot drop table >{tableName}<.\nException from {type(e).__module__}: {type(e).__name__}> {e}"
185
+ )
186
+ self._conn.execute(f"""CREATE TABLE {tableName}({tableCols});""")
187
+
188
+ if self._traceValue > 1:
189
+ self._traceLog(
190
+ f"Created new table: >{tableName}< with columns: >{tableCols}<"
191
+ )
192
+
193
+ def _insert_data(self, df: pd.DataFrame, writeFunction_args: dict):
194
+ placeHolder = "?," * (len(df.columns) - 1)
195
+ tableName = writeFunction_args["name"]
196
+ if df.isnull().values.any(): # replace NaN with None, for SQL NULL
197
+ df = df.astype(object).where(pd.notnull(df), None)
198
+ df_list = list(
199
+ df.itertuples(index=False, name=None)
200
+ ) # sql server does not accept nested lists, it has to be tuples
201
+ if writeFunction_args["schema"]:
202
+ tableName = writeFunction_args["schema"] + "." + tableName
203
+ query = f"INSERT INTO {tableName} VALUES(" + placeHolder + "?)"
204
+ if len(df_list) > 0:
205
+ self._conn.executemany(query, df_list)
206
+
207
+ elif self._traceValue > 1:
208
+ self._traceLog(
209
+ f"Empty symbol. No rows were inserted in table >{tableName}<."
210
+ )
211
+
212
+ def pre_write_procedures(self, **kwargs) -> gt.Container:
213
+ try:
214
+ container = kwargs["container"]
215
+ directory = kwargs["directory"]
216
+ sym_list = kwargs["sym_list"]
217
+ except KeyError as e:
218
+ self._raise_error(f"Key error in pre_write_procedures: {e}.")
219
+
220
+ if self._smallFlag:
221
+ write_container = gt.Container(system_directory=directory)
222
+ write_container.read(container, symbols=sym_list)
223
+ self._conn.execute("PRAGMA page_size = 1024;")
224
+ self._sqlite_create_uel_table(cc=write_container)
225
+ self._create_table(
226
+ df=self._uel_table_df,
227
+ tableName="[UEL$]",
228
+ schema=None,
229
+ ifExists="fail",
230
+ )
231
+ self._insert_data(
232
+ df=self._uel_table_df,
233
+ writeFunction_args={
234
+ "name": "[UEL$]",
235
+ "schema": None,
236
+ },
237
+ )
238
+ if not self._globalCommit:
239
+ self._engine.commit()
240
+
241
+ return write_container
242
+
243
+ elif self._check_table_exists(tableName="UEL$", schema=None):
244
+ message = (
245
+ "WARNING: The table >UEL$< already exists in the database file. "
246
+ "It appears that the database was created with the small option enabled. "
247
+ "Appending to or replacing a pre-existing table may lead to unexpected results."
248
+ )
249
+ self._traceLog(message)
250
+
251
+ return container
252
+
253
+ def post_write_procedures(self, **kwargs) -> None:
254
+ tableName = kwargs["tableName"]
255
+ dim_after_unstack = kwargs["dim_after_unstack"]
256
+ colList = kwargs["colList"]
257
+ dim = kwargs["dim"]
258
+ # After the parent tables <tableName$> are created. Views for the symbol name are to be created.
259
+ if self._smallFlag and dim > 0:
260
+ self._sqlite_create_view(
261
+ viewName=tableName, dim=dim_after_unstack, df_cols=colList
262
+ )