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.
- gams/__init__.py +27 -0
- gams/_version.py +1 -0
- gams/connect/__init__.py +28 -0
- gams/connect/agents/__init__.py +24 -0
- gams/connect/agents/_excel/__init__.py +32 -0
- gams/connect/agents/_excel/excelagent.py +312 -0
- gams/connect/agents/_excel/workbook.py +155 -0
- gams/connect/agents/_sqlconnectors/__init__.py +42 -0
- gams/connect/agents/_sqlconnectors/_accesshandler.py +211 -0
- gams/connect/agents/_sqlconnectors/_databasehandler.py +250 -0
- gams/connect/agents/_sqlconnectors/_mysqlhandler.py +168 -0
- gams/connect/agents/_sqlconnectors/_postgreshandler.py +131 -0
- gams/connect/agents/_sqlconnectors/_pyodbchandler.py +112 -0
- gams/connect/agents/_sqlconnectors/_sqlalchemyhandler.py +74 -0
- gams/connect/agents/_sqlconnectors/_sqlitehandler.py +262 -0
- gams/connect/agents/_sqlconnectors/_sqlserverhandler.py +179 -0
- gams/connect/agents/concatenate.py +440 -0
- gams/connect/agents/connectagent.py +743 -0
- gams/connect/agents/csvreader.py +675 -0
- gams/connect/agents/csvwriter.py +151 -0
- gams/connect/agents/domainwriter.py +143 -0
- gams/connect/agents/excelreader.py +756 -0
- gams/connect/agents/excelwriter.py +467 -0
- gams/connect/agents/filter.py +223 -0
- gams/connect/agents/gamsreader.py +112 -0
- gams/connect/agents/gamswriter.py +239 -0
- gams/connect/agents/gdxreader.py +109 -0
- gams/connect/agents/gdxwriter.py +146 -0
- gams/connect/agents/labelmanipulator.py +303 -0
- gams/connect/agents/projection.py +539 -0
- gams/connect/agents/pythoncode.py +71 -0
- gams/connect/agents/rawcsvreader.py +248 -0
- gams/connect/agents/rawexcelreader.py +312 -0
- gams/connect/agents/schema/CSVReader.yaml +92 -0
- gams/connect/agents/schema/CSVWriter.yaml +44 -0
- gams/connect/agents/schema/Concatenate.yaml +52 -0
- gams/connect/agents/schema/DomainWriter.yaml +25 -0
- gams/connect/agents/schema/ExcelReader.yaml +121 -0
- gams/connect/agents/schema/ExcelWriter.yaml +78 -0
- gams/connect/agents/schema/Filter.yaml +74 -0
- gams/connect/agents/schema/GAMSReader.yaml +20 -0
- gams/connect/agents/schema/GAMSWriter.yaml +47 -0
- gams/connect/agents/schema/GDXReader.yaml +23 -0
- gams/connect/agents/schema/GDXWriter.yaml +32 -0
- gams/connect/agents/schema/LabelManipulator.yaml +99 -0
- gams/connect/agents/schema/Projection.yaml +24 -0
- gams/connect/agents/schema/PythonCode.yaml +6 -0
- gams/connect/agents/schema/RawCSVReader.yaml +34 -0
- gams/connect/agents/schema/RawExcelReader.yaml +42 -0
- gams/connect/agents/schema/SQLReader.yaml +75 -0
- gams/connect/agents/schema/SQLWriter.yaml +103 -0
- gams/connect/agents/sqlreader.py +301 -0
- gams/connect/agents/sqlwriter.py +276 -0
- gams/connect/connectdatabase.py +275 -0
- gams/connect/connectvalidator.py +93 -0
- gams/connect/errors.py +34 -0
- gams/control/__init__.py +136 -0
- gams/control/database.py +2231 -0
- gams/control/execution.py +1900 -0
- gams/control/options.py +2792 -0
- gams/control/workspace.py +1198 -0
- gams/core/__init__.py +24 -0
- gams/core/cfg/__init__.py +26 -0
- gams/core/cfg/_cfgmcc.cp312-win_amd64.pyd +0 -0
- gams/core/cfg/cfgmcc.py +519 -0
- gams/core/dct/__init__.py +26 -0
- gams/core/dct/_dctmcc.cp312-win_amd64.pyd +0 -0
- gams/core/dct/dctmcc.py +574 -0
- gams/core/embedded/__init__.py +26 -0
- gams/core/embedded/gamsemb.py +1024 -0
- gams/core/emp/__init__.py +24 -0
- gams/core/emp/emplexer.py +89 -0
- gams/core/emp/empyacc.py +281 -0
- gams/core/gdx/__init__.py +26 -0
- gams/core/gdx/_gdxcc.cp312-win_amd64.pyd +0 -0
- gams/core/gdx/gdxcc.py +866 -0
- gams/core/gev/__init__.py +26 -0
- gams/core/gev/_gevmcc.cp312-win_amd64.pyd +0 -0
- gams/core/gev/gevmcc.py +855 -0
- gams/core/gmd/__init__.py +26 -0
- gams/core/gmd/_gmdcc.cp312-win_amd64.pyd +0 -0
- gams/core/gmd/gmdcc.py +917 -0
- gams/core/gmo/__init__.py +26 -0
- gams/core/gmo/_gmomcc.cp312-win_amd64.pyd +0 -0
- gams/core/gmo/gmomcc.py +2046 -0
- gams/core/idx/__init__.py +26 -0
- gams/core/idx/_idxcc.cp312-win_amd64.pyd +0 -0
- gams/core/idx/idxcc.py +510 -0
- gams/core/numpy/__init__.py +29 -0
- gams/core/numpy/_gams2numpy.cp312-win_amd64.pyd +0 -0
- gams/core/numpy/gams2numpy.py +1048 -0
- gams/core/opt/__init__.py +26 -0
- gams/core/opt/_optcc.cp312-win_amd64.pyd +0 -0
- gams/core/opt/optcc.py +840 -0
- gams/engine/__init__.py +204 -0
- gams/engine/api/__init__.py +13 -0
- gams/engine/api/auth_api.py +7653 -0
- gams/engine/api/cleanup_api.py +751 -0
- gams/engine/api/default_api.py +887 -0
- gams/engine/api/hypercube_api.py +2629 -0
- gams/engine/api/jobs_api.py +5229 -0
- gams/engine/api/licenses_api.py +2220 -0
- gams/engine/api/namespaces_api.py +7783 -0
- gams/engine/api/usage_api.py +5627 -0
- gams/engine/api/users_api.py +5931 -0
- gams/engine/api_client.py +804 -0
- gams/engine/api_response.py +21 -0
- gams/engine/configuration.py +601 -0
- gams/engine/exceptions.py +216 -0
- gams/engine/models/__init__.py +86 -0
- gams/engine/models/bad_input.py +89 -0
- gams/engine/models/cleanable_job_result.py +104 -0
- gams/engine/models/cleanable_job_result_page.py +113 -0
- gams/engine/models/engine_license.py +107 -0
- gams/engine/models/files_not_found.py +93 -0
- gams/engine/models/forwarded_token_response.py +112 -0
- gams/engine/models/generic_key_value_pair.py +89 -0
- gams/engine/models/hypercube.py +160 -0
- gams/engine/models/hypercube_page.py +111 -0
- gams/engine/models/hypercube_summary.py +91 -0
- gams/engine/models/hypercube_token.py +97 -0
- gams/engine/models/identity_provider.py +107 -0
- gams/engine/models/identity_provider_ldap.py +121 -0
- gams/engine/models/identity_provider_oauth2.py +146 -0
- gams/engine/models/identity_provider_oauth2_scope.py +89 -0
- gams/engine/models/identity_provider_oauth2_with_secret.py +152 -0
- gams/engine/models/identity_provider_oidc.py +133 -0
- gams/engine/models/identity_provider_oidc_with_secret.py +143 -0
- gams/engine/models/inex.py +91 -0
- gams/engine/models/invitation.py +136 -0
- gams/engine/models/invitation_quota.py +106 -0
- gams/engine/models/invitation_token.py +87 -0
- gams/engine/models/job.py +165 -0
- gams/engine/models/job_no_text_entry.py +138 -0
- gams/engine/models/job_no_text_entry_page.py +111 -0
- gams/engine/models/license.py +91 -0
- gams/engine/models/log_piece.py +96 -0
- gams/engine/models/message.py +87 -0
- gams/engine/models/message_and_token.py +99 -0
- gams/engine/models/message_with_webhook_id.py +89 -0
- gams/engine/models/model_auth_token.py +87 -0
- gams/engine/models/model_configuration.py +125 -0
- gams/engine/models/model_default_instance.py +99 -0
- gams/engine/models/model_default_user_instance.py +98 -0
- gams/engine/models/model_hypercube_job.py +106 -0
- gams/engine/models/model_hypercube_usage.py +130 -0
- gams/engine/models/model_instance_info.py +116 -0
- gams/engine/models/model_instance_info_full.py +123 -0
- gams/engine/models/model_instance_pool_info.py +112 -0
- gams/engine/models/model_job_labels.py +179 -0
- gams/engine/models/model_job_usage.py +133 -0
- gams/engine/models/model_pool_usage.py +124 -0
- gams/engine/models/model_usage.py +115 -0
- gams/engine/models/model_user.py +96 -0
- gams/engine/models/model_userinstance_info.py +119 -0
- gams/engine/models/model_userinstancepool_info.py +95 -0
- gams/engine/models/model_version.py +91 -0
- gams/engine/models/models.py +120 -0
- gams/engine/models/namespace.py +104 -0
- gams/engine/models/namespace_quota.py +96 -0
- gams/engine/models/namespace_with_permission.py +96 -0
- gams/engine/models/not_found.py +91 -0
- gams/engine/models/password_policy.py +97 -0
- gams/engine/models/perm_and_username.py +89 -0
- gams/engine/models/quota.py +117 -0
- gams/engine/models/quota_exceeded.py +97 -0
- gams/engine/models/status_code_meaning.py +89 -0
- gams/engine/models/stream_entry.py +89 -0
- gams/engine/models/system_wide_license.py +92 -0
- gams/engine/models/text_entries.py +87 -0
- gams/engine/models/text_entry.py +101 -0
- gams/engine/models/time_span.py +95 -0
- gams/engine/models/time_span_pool_worker.py +99 -0
- gams/engine/models/token_forward_error.py +87 -0
- gams/engine/models/user.py +127 -0
- gams/engine/models/user_group_member.py +96 -0
- gams/engine/models/user_groups.py +108 -0
- gams/engine/models/vapid_info.py +87 -0
- gams/engine/models/webhook.py +138 -0
- gams/engine/models/webhook_parameterized_event.py +99 -0
- gams/engine/py.typed +0 -0
- gams/engine/rest.py +258 -0
- gams/magic/__init__.py +32 -0
- gams/magic/gams_magic.py +142 -0
- gams/magic/interactive.py +402 -0
- gams/tools/__init__.py +30 -0
- gams/tools/errors.py +34 -0
- gams/tools/toolcollection/__init__.py +24 -0
- gams/tools/toolcollection/alg/__init__.py +24 -0
- gams/tools/toolcollection/alg/rank.py +51 -0
- gams/tools/toolcollection/data/__init__.py +24 -0
- gams/tools/toolcollection/data/csvread.py +444 -0
- gams/tools/toolcollection/data/csvwrite.py +311 -0
- gams/tools/toolcollection/data/exceldump.py +47 -0
- gams/tools/toolcollection/data/sqlitewrite.py +276 -0
- gams/tools/toolcollection/gdxservice/__init__.py +24 -0
- gams/tools/toolcollection/gdxservice/gdxencoding.py +104 -0
- gams/tools/toolcollection/gdxservice/gdxrename.py +94 -0
- gams/tools/toolcollection/linalg/__init__.py +24 -0
- gams/tools/toolcollection/linalg/cholesky.py +57 -0
- gams/tools/toolcollection/linalg/eigenvalue.py +56 -0
- gams/tools/toolcollection/linalg/eigenvector.py +58 -0
- gams/tools/toolcollection/linalg/invert.py +55 -0
- gams/tools/toolcollection/linalg/ols.py +138 -0
- gams/tools/toolcollection/tooltemplate.py +321 -0
- gams/tools/toolcollection/win32/__init__.py +24 -0
- gams/tools/toolcollection/win32/excelmerge.py +93 -0
- gams/tools/toolcollection/win32/exceltalk.py +76 -0
- gams/tools/toolcollection/win32/msappavail.py +49 -0
- gams/tools/toolcollection/win32/shellexecute.py +54 -0
- gams/tools/tools.py +116 -0
- gams/transfer/__init__.py +35 -0
- gams/transfer/_abcs/__init__.py +37 -0
- gams/transfer/_abcs/container_abcs.py +433 -0
- gams/transfer/_internals/__init__.py +63 -0
- gams/transfer/_internals/algorithms.py +436 -0
- gams/transfer/_internals/casepreservingdict.py +124 -0
- gams/transfer/_internals/constants.py +270 -0
- gams/transfer/_internals/domainviolation.py +103 -0
- gams/transfer/_internals/specialvalues.py +172 -0
- gams/transfer/containers/__init__.py +26 -0
- gams/transfer/containers/_container.py +1794 -0
- gams/transfer/containers/_io/__init__.py +28 -0
- gams/transfer/containers/_io/containers.py +164 -0
- gams/transfer/containers/_io/gdx.py +1029 -0
- gams/transfer/containers/_io/gmd.py +872 -0
- gams/transfer/containers/_mixins/__init__.py +26 -0
- gams/transfer/containers/_mixins/ccc.py +1274 -0
- gams/transfer/syms/__init__.py +33 -0
- gams/transfer/syms/_methods/__init__.py +24 -0
- gams/transfer/syms/_methods/tables.py +120 -0
- gams/transfer/syms/_methods/toDict.py +115 -0
- gams/transfer/syms/_methods/toList.py +83 -0
- gams/transfer/syms/_methods/toValue.py +60 -0
- gams/transfer/syms/_mixins/__init__.py +32 -0
- gams/transfer/syms/_mixins/equals.py +626 -0
- gams/transfer/syms/_mixins/generateRecords.py +499 -0
- gams/transfer/syms/_mixins/pivot.py +313 -0
- gams/transfer/syms/_mixins/pve.py +627 -0
- gams/transfer/syms/_mixins/sa.py +27 -0
- gams/transfer/syms/_mixins/sapve.py +27 -0
- gams/transfer/syms/_mixins/saua.py +27 -0
- gams/transfer/syms/_mixins/sauapve.py +199 -0
- gams/transfer/syms/_mixins/spve.py +1528 -0
- gams/transfer/syms/_mixins/ve.py +936 -0
- gams/transfer/syms/container_syms/__init__.py +31 -0
- gams/transfer/syms/container_syms/_alias.py +984 -0
- gams/transfer/syms/container_syms/_equation.py +333 -0
- gams/transfer/syms/container_syms/_parameter.py +973 -0
- gams/transfer/syms/container_syms/_set.py +604 -0
- gams/transfer/syms/container_syms/_universe_alias.py +461 -0
- gams/transfer/syms/container_syms/_variable.py +321 -0
- gamsapi-52.5.0.dist-info/METADATA +150 -0
- gamsapi-52.5.0.dist-info/RECORD +257 -0
- gamsapi-52.5.0.dist-info/WHEEL +5 -0
- gamsapi-52.5.0.dist-info/licenses/LICENSE +22 -0
- gamsapi-52.5.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,675 @@
|
|
|
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
|
|
15
|
+
# all 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 copy
|
|
27
|
+
import os
|
|
28
|
+
from typing import Any, List, Optional, Union
|
|
29
|
+
import pandas as pd
|
|
30
|
+
from gams import transfer as gt
|
|
31
|
+
from gams.connect.agents.connectagent import ConnectAgent
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class CSVReader(ConnectAgent):
|
|
35
|
+
def __init__(self, cdb, inst, agent_index):
|
|
36
|
+
super().__init__(cdb, inst, agent_index)
|
|
37
|
+
self._parse_options(self._inst)
|
|
38
|
+
|
|
39
|
+
def _parse_options(self, inst):
|
|
40
|
+
inst["file"] = os.path.abspath(inst["file"])
|
|
41
|
+
self._file = inst["file"]
|
|
42
|
+
self._name = inst["name"]
|
|
43
|
+
self._names = inst["names"]
|
|
44
|
+
self._sym_type = inst["type"]
|
|
45
|
+
self._index_cols = inst["indexColumns"]
|
|
46
|
+
self._index_sub = self._dict_get(inst, "indexSubstitutions", {})
|
|
47
|
+
self._value_cols = inst["valueColumns"]
|
|
48
|
+
self._value_sub = self._dict_get(inst, "valueSubstitutions", {})
|
|
49
|
+
self._trace = inst["trace"]
|
|
50
|
+
self._header = inst["header"]
|
|
51
|
+
self._skip_rows = inst["skipRows"]
|
|
52
|
+
self._read_csv_arguments = self._dict_get(inst, "readCSVArguments", {})
|
|
53
|
+
self._field_sep = inst["fieldSeparator"]
|
|
54
|
+
self._decimal_sep = inst["decimalSeparator"]
|
|
55
|
+
self._thousands_sep = inst["thousandsSeparator"]
|
|
56
|
+
self._quoting = inst["quoting"]
|
|
57
|
+
self._auto_col = inst["autoColumn"]
|
|
58
|
+
self._auto_row = inst["autoRow"]
|
|
59
|
+
self._stack = inst["stack"]
|
|
60
|
+
self._multiheader = True if isinstance(self._header, list) else False
|
|
61
|
+
self._header = self._parse_header()
|
|
62
|
+
|
|
63
|
+
if isinstance(self._skip_rows, list):
|
|
64
|
+
# pandas skiprows is 0-indexed
|
|
65
|
+
self._skip_rows = [i - 1 for i in self._skip_rows]
|
|
66
|
+
|
|
67
|
+
self._read_csv_args = {
|
|
68
|
+
"header": self._header,
|
|
69
|
+
"names": self._names,
|
|
70
|
+
"skiprows": self._skip_rows,
|
|
71
|
+
"sep": self._field_sep,
|
|
72
|
+
"decimal": self._decimal_sep,
|
|
73
|
+
"thousands": self._thousands_sep,
|
|
74
|
+
"quoting": self._quoting,
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
self._check_invalid_input()
|
|
78
|
+
self._read_csv_args.update(self._read_csv_arguments)
|
|
79
|
+
|
|
80
|
+
def _check_invalid_input(self):
|
|
81
|
+
if self._multiheader:
|
|
82
|
+
if self._sym_type == "set":
|
|
83
|
+
self._connect_error(
|
|
84
|
+
"Reading sets with multi-row headers is not supported."
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
if self._stack is False:
|
|
88
|
+
self._connect_error(
|
|
89
|
+
f"Multi-row header needs to be stacked to index but stack is set to >{self._stack}<."
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
if self._value_cols:
|
|
93
|
+
self._connect_error(
|
|
94
|
+
"Cannot specify valueColumns if the data has a multi-row"
|
|
95
|
+
" header. All columns that are not indexColumns will be"
|
|
96
|
+
" read as valueColumns."
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
if isinstance(self._index_cols, list) and all(
|
|
100
|
+
isinstance(col, str) for col in self._index_cols
|
|
101
|
+
):
|
|
102
|
+
self._connect_error(
|
|
103
|
+
"The CSVReader with multi-row header does not support to"
|
|
104
|
+
" specify indexColumns as column names. Please provide"
|
|
105
|
+
" column positions instead."
|
|
106
|
+
)
|
|
107
|
+
# consequently, multi-row header also does not support order of valueColumns
|
|
108
|
+
# and the same column in both indexColumns and valueColumns
|
|
109
|
+
elif self._sym_type == "par" and (
|
|
110
|
+
self._value_cols is None or self._value_cols == []
|
|
111
|
+
):
|
|
112
|
+
self._connect_error(
|
|
113
|
+
"Symbol type parameter requires at least one value column."
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
if self._multiheader and self._auto_col:
|
|
117
|
+
self._connect_error("Cannot use autoColumn with multi-row header.")
|
|
118
|
+
|
|
119
|
+
if self._multiheader and self._auto_row:
|
|
120
|
+
self._connect_error("Cannot use autoRow with multi-row header.")
|
|
121
|
+
|
|
122
|
+
if (
|
|
123
|
+
self._stack is True
|
|
124
|
+
and self._header is None
|
|
125
|
+
and self._names is None
|
|
126
|
+
and self._auto_col is None
|
|
127
|
+
):
|
|
128
|
+
self._connect_error("Cannot stack without a header, names or autoColumn.")
|
|
129
|
+
|
|
130
|
+
def _parse_header(self) -> Optional[Union[int, list]]:
|
|
131
|
+
"""
|
|
132
|
+
Returns the header value that will be provided to pandas
|
|
133
|
+
read_csv method according to the user input
|
|
134
|
+
|
|
135
|
+
Returns
|
|
136
|
+
-------
|
|
137
|
+
int | list | None
|
|
138
|
+
Header value
|
|
139
|
+
"""
|
|
140
|
+
|
|
141
|
+
# Infer: header True (0) if no names are provided otherwise False (None)
|
|
142
|
+
if self._header == "infer":
|
|
143
|
+
if self._names is None:
|
|
144
|
+
return 0
|
|
145
|
+
return None
|
|
146
|
+
|
|
147
|
+
# For non-multirow header, user inputs are booleans (True or False).
|
|
148
|
+
if self._header is False:
|
|
149
|
+
return None
|
|
150
|
+
|
|
151
|
+
if self._header is True:
|
|
152
|
+
return 0
|
|
153
|
+
|
|
154
|
+
if isinstance(self._header, list):
|
|
155
|
+
# pandas header is 0-indexed
|
|
156
|
+
return [i - 1 for i in self._header]
|
|
157
|
+
|
|
158
|
+
def _get_last_column(self) -> str:
|
|
159
|
+
"""Returns the position of the last column
|
|
160
|
+
|
|
161
|
+
Returns
|
|
162
|
+
-------
|
|
163
|
+
str
|
|
164
|
+
Position of the last column
|
|
165
|
+
"""
|
|
166
|
+
read_header_args = copy.deepcopy(self._read_csv_args)
|
|
167
|
+
if self._header is not None or self._names is not None:
|
|
168
|
+
read_header_args.update({"nrows": 0})
|
|
169
|
+
|
|
170
|
+
if self._trace > 1:
|
|
171
|
+
self._cdb.print_log(
|
|
172
|
+
"Calculate symbolic constant lastCol by reading the"
|
|
173
|
+
" header row. Arguments for reading the header"
|
|
174
|
+
f" row:\n{read_header_args}"
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
else:
|
|
178
|
+
read_header_args.update({"header": None, "nrows": 1})
|
|
179
|
+
|
|
180
|
+
if self._trace > 1:
|
|
181
|
+
self._cdb.print_log(
|
|
182
|
+
"Calculate symbolic constant lastCol by reading the"
|
|
183
|
+
" first line of data. Arguments for reading the first"
|
|
184
|
+
f" line of data:\n{read_header_args}"
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
header_row = pd.read_csv(self._file, **read_header_args)
|
|
188
|
+
|
|
189
|
+
return str(len(header_row.columns))
|
|
190
|
+
|
|
191
|
+
def _convert_to_valid_pd_cols(self, cols: Any) -> Union[List[int], List[str]]:
|
|
192
|
+
"""Converts user provided indexColumns or valueColumns
|
|
193
|
+
to valid pandas columns
|
|
194
|
+
|
|
195
|
+
Parameters
|
|
196
|
+
----------
|
|
197
|
+
cols : Any
|
|
198
|
+
Columns provided by the user
|
|
199
|
+
|
|
200
|
+
Returns
|
|
201
|
+
-------
|
|
202
|
+
list
|
|
203
|
+
Column positions or names
|
|
204
|
+
"""
|
|
205
|
+
|
|
206
|
+
if isinstance(cols, str):
|
|
207
|
+
if "lastcol" in cols.lower():
|
|
208
|
+
last_col = self._get_last_column()
|
|
209
|
+
cols = cols.lower().replace("lastcol", last_col)
|
|
210
|
+
try:
|
|
211
|
+
positions = [
|
|
212
|
+
(
|
|
213
|
+
list(
|
|
214
|
+
range(
|
|
215
|
+
*[
|
|
216
|
+
int(value) + index
|
|
217
|
+
for index, value in enumerate(position.split(":"))
|
|
218
|
+
]
|
|
219
|
+
)
|
|
220
|
+
)
|
|
221
|
+
if ":" in position
|
|
222
|
+
else [int(position)]
|
|
223
|
+
)
|
|
224
|
+
for position in cols.split(",")
|
|
225
|
+
]
|
|
226
|
+
|
|
227
|
+
# convert to 0-indexed
|
|
228
|
+
cols = [i - 1 for sublist in positions for i in sublist]
|
|
229
|
+
except Exception:
|
|
230
|
+
self._connect_error(
|
|
231
|
+
"Column assignation as string can only include"
|
|
232
|
+
" integers, comma (,), colon (:) and symbolic constant"
|
|
233
|
+
" lastCol."
|
|
234
|
+
)
|
|
235
|
+
elif isinstance(cols, list):
|
|
236
|
+
if all(isinstance(i, int) for i in cols):
|
|
237
|
+
cols = [i - 1 for i in cols] # convert to 0-indexed
|
|
238
|
+
elif isinstance(cols, int):
|
|
239
|
+
cols = [cols - 1]
|
|
240
|
+
else:
|
|
241
|
+
cols = []
|
|
242
|
+
|
|
243
|
+
return cols
|
|
244
|
+
|
|
245
|
+
def _check_cols(self):
|
|
246
|
+
if self._stack is False and len(self._value_cols) > 1:
|
|
247
|
+
self._connect_error(
|
|
248
|
+
f"For more than one value column the column names need to be stacked to index but stack is set to >{self._stack}<."
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
if not all(
|
|
252
|
+
isinstance(i, str) for i in self._index_cols + self._value_cols
|
|
253
|
+
) and not all(isinstance(i, int) for i in self._index_cols + self._value_cols):
|
|
254
|
+
self._connect_error(
|
|
255
|
+
"Index and value columns must be either given as positions or"
|
|
256
|
+
" names not both."
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
if len(self._index_cols) != len(set(self._index_cols)) or len(
|
|
260
|
+
self._value_cols
|
|
261
|
+
) != len(set(self._value_cols)):
|
|
262
|
+
self._connect_error("Duplicates in index and value columns not allowed.")
|
|
263
|
+
|
|
264
|
+
if len(self._value_cols) > 1:
|
|
265
|
+
if self._header is None and self._names is None and self._auto_col is None:
|
|
266
|
+
self._connect_error(
|
|
267
|
+
"More than one value column requires a header, names"
|
|
268
|
+
" or autoColumn."
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
def _get_dtypes(
|
|
272
|
+
self,
|
|
273
|
+
index_col: Optional[Union[List[int], List[str]]],
|
|
274
|
+
usecols: Union[List[int], List[str]],
|
|
275
|
+
) -> dict:
|
|
276
|
+
"""Returns the data types for index and text columns
|
|
277
|
+
|
|
278
|
+
Parameters
|
|
279
|
+
----------
|
|
280
|
+
index_col : List[int] | List[str] | None
|
|
281
|
+
Index columns to be used for read_csv
|
|
282
|
+
usecols : List[int] | List[str]
|
|
283
|
+
All columns to be used
|
|
284
|
+
|
|
285
|
+
Returns
|
|
286
|
+
-------
|
|
287
|
+
dict
|
|
288
|
+
Updated data types for columns
|
|
289
|
+
"""
|
|
290
|
+
col_dtype = {}
|
|
291
|
+
|
|
292
|
+
if index_col is not None:
|
|
293
|
+
col_dtype.update({i: str for i in index_col})
|
|
294
|
+
|
|
295
|
+
if self._sym_type == "set" and len(self._value_cols) > 0:
|
|
296
|
+
value_col = [sorted(usecols).index(i) for i in self._value_cols]
|
|
297
|
+
col_dtype.update({i: str for i in value_col})
|
|
298
|
+
|
|
299
|
+
return col_dtype
|
|
300
|
+
|
|
301
|
+
def _substitute_index(self, df: pd.DataFrame) -> None:
|
|
302
|
+
"""Inplace index substitution
|
|
303
|
+
|
|
304
|
+
Parameters
|
|
305
|
+
----------
|
|
306
|
+
df : pd.DataFrame
|
|
307
|
+
DataFrame to be manipulated
|
|
308
|
+
"""
|
|
309
|
+
if self._sym_type == "set" and len(self._value_cols) == 0:
|
|
310
|
+
df.replace(self._index_sub, inplace=True)
|
|
311
|
+
else:
|
|
312
|
+
df.iloc[:, :-1] = df.iloc[:, :-1].replace(self._index_sub)
|
|
313
|
+
|
|
314
|
+
if self._trace > 2:
|
|
315
|
+
self._cdb.print_log(f"DataFrame after index substitution:\n{df}")
|
|
316
|
+
|
|
317
|
+
def _substitute_values(self, df: pd.DataFrame):
|
|
318
|
+
"""Inplace value substitution
|
|
319
|
+
|
|
320
|
+
Parameters
|
|
321
|
+
----------
|
|
322
|
+
df : pd.DataFrame
|
|
323
|
+
DataFrame to be manipulated
|
|
324
|
+
"""
|
|
325
|
+
# pandas-version-check
|
|
326
|
+
if self._pandas_version_before(pd.__version__, "2.2"): # pandas < 2.2.0
|
|
327
|
+
df.isetitem(-1, df.iloc[:, -1].replace(self._value_sub))
|
|
328
|
+
else: # pandas >= 2.2.0
|
|
329
|
+
with pd.option_context("future.no_silent_downcasting", True):
|
|
330
|
+
df.isetitem(-1, df.iloc[:, -1].replace(self._value_sub).infer_objects())
|
|
331
|
+
|
|
332
|
+
if self._trace > 2:
|
|
333
|
+
self._cdb.print_log(f"DataFrame after value substitution:\n{df}")
|
|
334
|
+
|
|
335
|
+
def _generate_row_labels(self, df: pd.DataFrame) -> None:
|
|
336
|
+
"""Generates row labels for autoRow option
|
|
337
|
+
|
|
338
|
+
Parameters
|
|
339
|
+
----------
|
|
340
|
+
df : pd.DataFrame
|
|
341
|
+
DataFrame to be manipulated
|
|
342
|
+
"""
|
|
343
|
+
if self._index_cols:
|
|
344
|
+
index_frame = df.index.to_frame()
|
|
345
|
+
index_frame.insert(
|
|
346
|
+
0,
|
|
347
|
+
"autoRow",
|
|
348
|
+
[self._auto_row + str(i + 1) for i in range(len(df.index))],
|
|
349
|
+
True,
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
df.index = pd.MultiIndex.from_frame(index_frame)
|
|
353
|
+
else:
|
|
354
|
+
df.rename(
|
|
355
|
+
{i: self._auto_row + str(i + 1) for i in list(df.index)},
|
|
356
|
+
axis="index",
|
|
357
|
+
inplace=True,
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
if self._trace > 2:
|
|
361
|
+
self._cdb.print_log(f"DataFrame after inserting autoRow:\n{df}")
|
|
362
|
+
|
|
363
|
+
def _generate_column_labels(self, df: pd.DataFrame) -> None:
|
|
364
|
+
"""Generates columns labels for autoColumn option
|
|
365
|
+
|
|
366
|
+
Parameters
|
|
367
|
+
----------
|
|
368
|
+
df : pd.DataFrame
|
|
369
|
+
DataFrame to be manipulated
|
|
370
|
+
"""
|
|
371
|
+
if (self._header is not None or self._names is not None) and self._trace > 1:
|
|
372
|
+
self._cdb.print_log("autoColumn overrides existing column names.")
|
|
373
|
+
|
|
374
|
+
df.rename(
|
|
375
|
+
{c: self._auto_col + str(i + 1) for i, c in enumerate(list(df.columns))},
|
|
376
|
+
axis="columns",
|
|
377
|
+
inplace=True,
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
if self._trace > 2:
|
|
381
|
+
self._cdb.print_log(f"DataFrame after inserting autoColumn:\n{df}")
|
|
382
|
+
|
|
383
|
+
def _set_categoricals(
|
|
384
|
+
self, symbol: Union["gt.Set", "gt.Parameter"], dim: int, columns: list
|
|
385
|
+
) -> None:
|
|
386
|
+
"""Sets the categoricals for the Gams Transfer symbol records
|
|
387
|
+
|
|
388
|
+
Parameters
|
|
389
|
+
----------
|
|
390
|
+
symbol : gt.Set | gt.Parameter
|
|
391
|
+
Symbol for setting the categoricals
|
|
392
|
+
dim : int
|
|
393
|
+
Dimension to set the categories on
|
|
394
|
+
columns : list
|
|
395
|
+
Column names for categories
|
|
396
|
+
"""
|
|
397
|
+
symbol.records.isetitem(
|
|
398
|
+
dim,
|
|
399
|
+
symbol.records.iloc[:, dim].astype(
|
|
400
|
+
pd.CategoricalDtype(
|
|
401
|
+
categories=columns.map(str).map(str.rstrip).unique(),
|
|
402
|
+
ordered=True,
|
|
403
|
+
)
|
|
404
|
+
),
|
|
405
|
+
)
|
|
406
|
+
|
|
407
|
+
def _stack_multiheader(self, df: pd.DataFrame) -> pd.DataFrame:
|
|
408
|
+
"""Stacks column names to index for a multi-row header
|
|
409
|
+
|
|
410
|
+
Parameters
|
|
411
|
+
----------
|
|
412
|
+
df : pd.DataFrame
|
|
413
|
+
DataFrame to be stacked
|
|
414
|
+
|
|
415
|
+
Returns
|
|
416
|
+
-------
|
|
417
|
+
pd.DataFrame
|
|
418
|
+
Stacked DataFrame
|
|
419
|
+
"""
|
|
420
|
+
# Make index and column names unique
|
|
421
|
+
df.columns.names = [f"column_{i}" for i in range(len(df.columns.names))]
|
|
422
|
+
|
|
423
|
+
df.index.names = [f"row_{i}" for i in range(len(df.index.names))]
|
|
424
|
+
|
|
425
|
+
# Stack columns to index
|
|
426
|
+
# pandas-version-check
|
|
427
|
+
if self._pandas_version_before(pd.__version__, "2.2"): # pandas < 2.2.0
|
|
428
|
+
df = df.stack(level=df.columns.names, dropna=False)
|
|
429
|
+
else: # pandas >= 2.2.0
|
|
430
|
+
df = df.stack(level=df.columns.names, future_stack=True)
|
|
431
|
+
|
|
432
|
+
return df
|
|
433
|
+
|
|
434
|
+
def _sort_value_columns(self, df: pd.DataFrame) -> pd.DataFrame:
|
|
435
|
+
"""Sorts value columns according to the order specified
|
|
436
|
+
|
|
437
|
+
Parameters
|
|
438
|
+
----------
|
|
439
|
+
df : pd.DataFrame
|
|
440
|
+
DataFrame to be sorted
|
|
441
|
+
|
|
442
|
+
Returns
|
|
443
|
+
-------
|
|
444
|
+
pd.DataFrame
|
|
445
|
+
DataFrame with sorted value columns
|
|
446
|
+
"""
|
|
447
|
+
value_cols = [c for c in self._value_cols if c not in self._index_cols]
|
|
448
|
+
|
|
449
|
+
if len(value_cols) > 1:
|
|
450
|
+
if all(isinstance(i, int) for i in value_cols):
|
|
451
|
+
value_cols_order = [
|
|
452
|
+
df.columns[sorted(value_cols).index(i)] for i in value_cols
|
|
453
|
+
]
|
|
454
|
+
else:
|
|
455
|
+
value_cols_order = value_cols
|
|
456
|
+
|
|
457
|
+
df = df[value_cols_order]
|
|
458
|
+
|
|
459
|
+
if self._trace > 2:
|
|
460
|
+
self._cdb.print_log(f"DataFrame after reordering value columns:\n{df}")
|
|
461
|
+
|
|
462
|
+
return df
|
|
463
|
+
|
|
464
|
+
def _copy_from_index_to_value(
|
|
465
|
+
self, df: pd.DataFrame, index_col, usecols
|
|
466
|
+
) -> pd.DataFrame:
|
|
467
|
+
"""If columns appear both in index and value columns, pd.read_csv only
|
|
468
|
+
adds the column to index and we copy it to the value column
|
|
469
|
+
|
|
470
|
+
Parameters
|
|
471
|
+
----------
|
|
472
|
+
df : pd.DataFrame
|
|
473
|
+
DataFrame to be extended
|
|
474
|
+
index_col : list | None
|
|
475
|
+
argument passed to pd.read_csv method
|
|
476
|
+
usecols : list
|
|
477
|
+
argument passed to pd.read_csv method
|
|
478
|
+
|
|
479
|
+
Returns
|
|
480
|
+
-------
|
|
481
|
+
pd.DataFrame
|
|
482
|
+
Extended DataFrame
|
|
483
|
+
"""
|
|
484
|
+
if any(c in self._value_cols for c in self._index_cols):
|
|
485
|
+
value_cols = self._value_cols
|
|
486
|
+
if all(isinstance(i, int) for i in value_cols):
|
|
487
|
+
value_cols = [sorted(usecols).index(i) for i in value_cols]
|
|
488
|
+
for i, c in enumerate(value_cols):
|
|
489
|
+
if c in index_col:
|
|
490
|
+
index_name = df.index.names[index_col.index(c)]
|
|
491
|
+
df.insert(i, index_name, df.index.get_level_values(index_name))
|
|
492
|
+
if self._trace > 2:
|
|
493
|
+
self._cdb.print_log(
|
|
494
|
+
f"DataFrame after duplicating index to value column:\n{df}"
|
|
495
|
+
)
|
|
496
|
+
|
|
497
|
+
return df
|
|
498
|
+
|
|
499
|
+
def execute(self):
|
|
500
|
+
if self._trace > 0:
|
|
501
|
+
self._log_instructions(self._inst, self._inst_raw)
|
|
502
|
+
self._describe_container(self._cdb.container, "Connect Container (before):")
|
|
503
|
+
|
|
504
|
+
self._symbols_exist_cdb(self._name)
|
|
505
|
+
|
|
506
|
+
self._index_cols = self._convert_to_valid_pd_cols(self._index_cols)
|
|
507
|
+
self._value_cols = self._convert_to_valid_pd_cols(self._value_cols)
|
|
508
|
+
self._check_cols()
|
|
509
|
+
|
|
510
|
+
if self._stack == "infer":
|
|
511
|
+
if len(self._value_cols) > 1 or self._multiheader:
|
|
512
|
+
self._stack = True
|
|
513
|
+
else:
|
|
514
|
+
self._stack = False
|
|
515
|
+
|
|
516
|
+
self._usecols = self._index_cols + self._value_cols
|
|
517
|
+
|
|
518
|
+
# no duplicates in usecols, since pd.read_csv ignores duplicates in usecols and the indices in index_col do not match anymore
|
|
519
|
+
self._usecols = list(dict.fromkeys(self._usecols))
|
|
520
|
+
|
|
521
|
+
if self._index_cols:
|
|
522
|
+
if all(isinstance(i, int) for i in self._usecols) and not self._multiheader:
|
|
523
|
+
self._index_col = [sorted(self._usecols).index(i) for i in self._index_cols]
|
|
524
|
+
else:
|
|
525
|
+
self._index_col = self._index_cols
|
|
526
|
+
else:
|
|
527
|
+
self._index_col = None
|
|
528
|
+
|
|
529
|
+
# default dtype of index and text columns should be string
|
|
530
|
+
if "dtype" not in self._read_csv_args.keys():
|
|
531
|
+
dtypes = self._get_dtypes(self._index_col, self._usecols)
|
|
532
|
+
self._read_csv_args.update({"dtype": dtypes})
|
|
533
|
+
|
|
534
|
+
self._read_csv_args.update({"index_col": self._index_col})
|
|
535
|
+
|
|
536
|
+
# Multi-row header does not support usecols, therefore, we only
|
|
537
|
+
# support reading all columns and not a subset
|
|
538
|
+
if not self._multiheader:
|
|
539
|
+
self._read_csv_args.update({"usecols": self._usecols})
|
|
540
|
+
|
|
541
|
+
if self._trace > 1:
|
|
542
|
+
self._cdb.print_log(
|
|
543
|
+
f"Arguments for reading the CSV file:\n{self._read_csv_args}"
|
|
544
|
+
)
|
|
545
|
+
|
|
546
|
+
df = pd.read_csv(self._file, **self._read_csv_args)
|
|
547
|
+
|
|
548
|
+
if self._trace > 2:
|
|
549
|
+
self._cdb.print_log(
|
|
550
|
+
f"Raw DataFrame directly after reading the CSV file:\n{df}"
|
|
551
|
+
)
|
|
552
|
+
|
|
553
|
+
df = self._sort_value_columns(df)
|
|
554
|
+
df = self._copy_from_index_to_value(df, self._index_col, self._usecols)
|
|
555
|
+
|
|
556
|
+
dim = len(self._index_cols)
|
|
557
|
+
# write relaxed domain information
|
|
558
|
+
if dim == 0:
|
|
559
|
+
domain = []
|
|
560
|
+
else:
|
|
561
|
+
domain = [str(d) if d is not None else "*" for d in df.index.names]
|
|
562
|
+
|
|
563
|
+
if self._auto_row is not None and not df.index.empty:
|
|
564
|
+
self._generate_row_labels(df)
|
|
565
|
+
|
|
566
|
+
dim += 1
|
|
567
|
+
domain.insert(0, "*")
|
|
568
|
+
|
|
569
|
+
if self._auto_col is not None and not df.columns.empty:
|
|
570
|
+
self._generate_column_labels(df)
|
|
571
|
+
|
|
572
|
+
if self._stack:
|
|
573
|
+
dim += 1
|
|
574
|
+
domain.append("*")
|
|
575
|
+
|
|
576
|
+
elif self._stack:
|
|
577
|
+
if self._multiheader:
|
|
578
|
+
dim += len(self._header)
|
|
579
|
+
domain.extend(
|
|
580
|
+
[str(d) if d is not None else "*" for d in df.columns.names]
|
|
581
|
+
)
|
|
582
|
+
else:
|
|
583
|
+
dim += 1
|
|
584
|
+
domain.append("*")
|
|
585
|
+
|
|
586
|
+
if dim > 0:
|
|
587
|
+
if self._stack:
|
|
588
|
+
columns = df.columns
|
|
589
|
+
|
|
590
|
+
# stack from column axis to index axis
|
|
591
|
+
if self._multiheader:
|
|
592
|
+
df = self._stack_multiheader(df)
|
|
593
|
+
else:
|
|
594
|
+
# pandas-version-check
|
|
595
|
+
if self._pandas_version_before(
|
|
596
|
+
pd.__version__, "2.2"
|
|
597
|
+
): # pandas < 2.2.0
|
|
598
|
+
df = df.stack(dropna=False)
|
|
599
|
+
else: # pandas >= 2.2.0
|
|
600
|
+
df = df.stack(future_stack=True)
|
|
601
|
+
|
|
602
|
+
if dim == 1 or (self._multiheader and dim == columns.nlevels):
|
|
603
|
+
# drop pandas default index level
|
|
604
|
+
df = df.droplevel(level=0)
|
|
605
|
+
|
|
606
|
+
if self._trace > 1:
|
|
607
|
+
self._cdb.print_log(
|
|
608
|
+
"Automatically stack column names to index for more"
|
|
609
|
+
" than one value column."
|
|
610
|
+
)
|
|
611
|
+
if self._trace > 2:
|
|
612
|
+
self._cdb.print_log(f"DataFrame after stack:\n{df}")
|
|
613
|
+
|
|
614
|
+
df = df.reset_index(allow_duplicates=True)
|
|
615
|
+
|
|
616
|
+
if self._trace > 2:
|
|
617
|
+
self._cdb.print_log(f"DataFrame after .reset_index():\n{df}")
|
|
618
|
+
|
|
619
|
+
# index substitution
|
|
620
|
+
if self._index_sub:
|
|
621
|
+
self._substitute_index(df)
|
|
622
|
+
|
|
623
|
+
# Substitute in categoricals
|
|
624
|
+
if self._stack:
|
|
625
|
+
if self._multiheader:
|
|
626
|
+
mod_columns = []
|
|
627
|
+
for i in range(columns.nlevels):
|
|
628
|
+
c = columns.get_level_values(level=i)
|
|
629
|
+
mod_columns.append(pd.Series(c).replace(self._index_sub))
|
|
630
|
+
columns = pd.MultiIndex.from_arrays(mod_columns)
|
|
631
|
+
else:
|
|
632
|
+
columns = pd.Index(pd.Series(columns).replace(self._index_sub))
|
|
633
|
+
|
|
634
|
+
elif self._sym_type == "set":
|
|
635
|
+
self._connect_error("Symbol type set requires at least one index column.")
|
|
636
|
+
|
|
637
|
+
if self._value_sub and (self._sym_type == "par" or self._value_cols):
|
|
638
|
+
self._substitute_values(df)
|
|
639
|
+
df.dropna(inplace=True)
|
|
640
|
+
|
|
641
|
+
if self._sym_type == "par":
|
|
642
|
+
sym = gt.Parameter(self._cdb.container, self._name, domain=domain)
|
|
643
|
+
else:
|
|
644
|
+
sym = gt.Set(self._cdb.container, self._name, domain=domain)
|
|
645
|
+
|
|
646
|
+
# reset the index to the default integer index
|
|
647
|
+
df = df.reset_index(drop=True)
|
|
648
|
+
|
|
649
|
+
if self._trace > 2:
|
|
650
|
+
self._cdb.print_log(
|
|
651
|
+
"Final DataFrame that will be processed by" f" GAMSTransfer:\n{df}"
|
|
652
|
+
)
|
|
653
|
+
|
|
654
|
+
sym.setRecords(df)
|
|
655
|
+
|
|
656
|
+
if dim > 0 and self._stack:
|
|
657
|
+
if self._multiheader:
|
|
658
|
+
# Reset domain labels
|
|
659
|
+
sym.domain_labels = domain
|
|
660
|
+
# Set categoricals to preserve uel order
|
|
661
|
+
for i in range(columns.nlevels):
|
|
662
|
+
c = columns.get_level_values(level=i)
|
|
663
|
+
c_dim = dim - columns.nlevels + i
|
|
664
|
+
self._set_categoricals(sym, c_dim, c)
|
|
665
|
+
else:
|
|
666
|
+
# Set categoricals to preserve uel order
|
|
667
|
+
self._set_categoricals(sym, dim - 1, columns)
|
|
668
|
+
|
|
669
|
+
if self._trace > 2:
|
|
670
|
+
self._cdb.print_log(
|
|
671
|
+
f"Connect Container symbol={self._name}:\n {sym.records}\n"
|
|
672
|
+
)
|
|
673
|
+
|
|
674
|
+
if self._trace > 0:
|
|
675
|
+
self._describe_container(self._cdb.container, "Connect Container (after):")
|