sqlalchemy-iris 0.15.5b1__py3-none-any.whl → 0.16.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -54,3 +54,6 @@ class _Column(intersystems_iris.dbapi._Descriptor._Descriptor):
54
54
  self.isKeyColumn,
55
55
  self.isRowId]
56
56
  return _Column(self.name, self.type, self.precision, self.scale, self.nullable, self.label, self.tableName, self.schema, self.catalog, additionalData, self.slotPosition)
57
+
58
+ def __repr__(self) -> str:
59
+ return f"<Column name='{self.name}' type='{self.type}'>"
@@ -14,7 +14,7 @@ from ._ResultSetRow import _ResultSetRow
14
14
  import intersystems_iris.dbapi._ParameterCollection
15
15
  import intersystems_iris.dbapi.preparser._PreParser
16
16
  from intersystems_iris.dbapi._Parameter import ParameterMode
17
- from intersystems_iris.dbapi.preparser._PreParser import StatementType, MultiValuesInsert
17
+ from intersystems_iris.dbapi.preparser._PreParser import StatementType, MultiValuesInsert, InsertWithReturning
18
18
  from intersystems_iris._IRISConnection import Feature
19
19
  from intersystems_iris._InStream import _InStream
20
20
  from intersystems_iris.dbapi._IRISStream import (
@@ -252,6 +252,11 @@ class _BaseCursor:
252
252
  self._cleanup()
253
253
  try:
254
254
  self._preparse()
255
+ except InsertWithReturning as ex:
256
+ ids = intersystems_iris._ListWriter._ListWriter()
257
+ self.execute(ex.insert_query, params)
258
+ ids._set(self.lastrowid)
259
+ return self.execute(ex.select_query, bytes(ids._get_buffer()))
255
260
  except MultiValuesInsert as ex:
256
261
  # convert to executemany
257
262
  params = params or ex.params
@@ -269,6 +274,7 @@ class _BaseCursor:
269
274
 
270
275
  self._execute()
271
276
  return self._rowcount
277
+ # return self.execute(returning_query)
272
278
 
273
279
  def add_batch(self):
274
280
  self._is_alive()
@@ -321,7 +327,17 @@ class _BaseCursor:
321
327
  self._cleanup()
322
328
  self._is_batch_update = True
323
329
 
324
- self._preparse()
330
+ try:
331
+ self._preparse()
332
+ except InsertWithReturning as ex:
333
+ ids = intersystems_iris._ListWriter._ListWriter()
334
+ for params in seq_of_params:
335
+ self.execute(ex.insert_query, params)
336
+ ids._set(self.lastrowid)
337
+ self.execute(ex.select_query, bytes(ids._get_buffer()))
338
+ raise
339
+ except Exception:
340
+ raise
325
341
  self._prepare()
326
342
 
327
343
  for row_num, param_row in enumerate(seq_of_params):
@@ -389,6 +405,8 @@ class _BaseCursor:
389
405
  )
390
406
  try:
391
407
  pOut = parser.PreParse(self.statement, self._params)
408
+ except InsertWithReturning:
409
+ raise
392
410
  except MultiValuesInsert:
393
411
  raise
394
412
  except Exception as e:
@@ -1182,6 +1200,8 @@ class Cursor(_BaseCursor):
1182
1200
  sets = self._parameter_sets or 1
1183
1201
  self.params = list(self.params).copy()
1184
1202
  param_types = [param.type for param in self._params._params_list]
1203
+ if not self.params:
1204
+ return
1185
1205
 
1186
1206
  for i in range(sets):
1187
1207
  params = self._params.collect(i)
@@ -1949,7 +1969,12 @@ class Cursor(_BaseCursor):
1949
1969
  self._closed = True
1950
1970
 
1951
1971
  def executemany(self, operation, seq_of_params):
1952
- super().executemany(operation, seq_of_params)
1972
+ try:
1973
+ super().executemany(operation, seq_of_params)
1974
+ except InsertWithReturning:
1975
+ return
1976
+ except Exception:
1977
+ raise
1953
1978
  self._rowcount = 0
1954
1979
  for i in range(len(self.params)):
1955
1980
  self._rowcount += self._in_message.wire._get()
@@ -2263,13 +2288,21 @@ class EmbdeddedCursor(_BaseCursor):
2263
2288
  def __del__(self):
2264
2289
  try:
2265
2290
  self.close()
2266
- except:
2291
+ except Exception:
2267
2292
  pass
2268
2293
  return
2269
2294
 
2270
2295
  def _get_cached_info(self):
2271
2296
  return False
2272
2297
 
2298
+ def executemany(self, operation, seq_of_params):
2299
+ try:
2300
+ super().executemany(operation, seq_of_params)
2301
+ except InsertWithReturning:
2302
+ return
2303
+ except Exception:
2304
+ raise
2305
+
2273
2306
  def _get_parameters(self, params_set=0):
2274
2307
  params = self._params.collect(params_set)
2275
2308
  # None = '', '' = b'\x00'
@@ -2394,6 +2427,7 @@ class EmbdeddedCursor(_BaseCursor):
2394
2427
  sqlcode = 0
2395
2428
  message = None
2396
2429
  try:
2430
+ print("!!!! _send_direct_query_request:params", [statement, params])
2397
2431
  self._result_set = self._sql.exec(statement, *params)
2398
2432
  self._rowcount = self._result_set.ResultSet._ROWCOUNT
2399
2433
  self._get_column_info()
@@ -58,6 +58,8 @@ class IRISStream:
58
58
  if not self._binary:
59
59
  result = str(result, self._locale)
60
60
 
61
+ if self._binary:
62
+ return bytes(result)
61
63
  return result
62
64
 
63
65
 
@@ -162,7 +162,7 @@ class _ResultSetRow:
162
162
  self._list_item.next_offset = self._offsets[key]
163
163
  _DBList._get_list_element(self._list_item)
164
164
  item = _DBList._get(self._list_item, self._locale)
165
- _column: _Column = self._columns[idx]
165
+ _column: _Column = self._columns[key]
166
166
  ctype = _column.type
167
167
  value_type = self._types[ctype] if ctype in self._types else None
168
168
  try:
@@ -11,6 +11,7 @@ from intersystems_iris.dbapi._Parameter import ParameterMode
11
11
  from intersystems_iris.dbapi.preparser._Token import TOKEN
12
12
  from intersystems_iris.dbapi.preparser._Scanner import ParseToken
13
13
 
14
+
14
15
  class MultiValuesInsert(Exception):
15
16
 
16
17
  def __init__(self, *args: object, query: str, rows: int, params=None) -> None:
@@ -20,6 +21,15 @@ class MultiValuesInsert(Exception):
20
21
  self.params = params
21
22
 
22
23
 
24
+ class InsertWithReturning(Exception):
25
+
26
+ def __init__(self, *args: object, insert_query: str, insert_params, select_query: str) -> None:
27
+ super().__init__(*args)
28
+ self.insert_query = insert_query
29
+ self.insert_params = insert_params
30
+ self.select_query = select_query
31
+
32
+
23
33
  # May want to move to its own file eventually
24
34
  # SQL Statement Types
25
35
  class StatementType(enum.IntEnum):
@@ -480,6 +490,11 @@ class _PreParser(object):
480
490
  if found_insert:
481
491
  self.Tokenize(t_query)
482
492
 
493
+ _, t_query, t_select_query = self.InsertReturning(t_query)
494
+ if t_select_query:
495
+ raise InsertWithReturning(insert_query=t_query, insert_params=p_Parameters, select_query=t_select_query)
496
+ self.Tokenize(t_query)
497
+
483
498
  # Resolve the tokens and determine output
484
499
  return self.Resolve(t_query, p_Parameters)
485
500
 
@@ -549,9 +564,39 @@ class _PreParser(object):
549
564
  new_query += _query(False)
550
565
 
551
566
  return found, new_query
552
- except:
567
+ except Exception:
553
568
  return False, query
554
-
569
+
570
+ def InsertReturning(self, query):
571
+ plain_query = ''
572
+
573
+ tokens = self.m_Tokens.GetEnumerator()
574
+ keywords = list(self.s_StatementTable.keys()) + list(self.s_ParsedStatements.keys()) + list(self.s_TransactionStatements.keys())
575
+ while tokens.MoveNext() and not tokens.Current().UpperOnOf(keywords):
576
+ plain_query += tokens.Current().Lexeme + ' '
577
+ if tokens.AtEnd() or not tokens.Current().UpperEquals("INSERT"):
578
+ return False, query, None
579
+
580
+ if not tokens.MoveNext() or not tokens.Current().UpperEquals("INTO"):
581
+ return False, query, None
582
+ plain_query += 'INSERT INTO '
583
+ table_name = ""
584
+ while tokens.MoveNext() and not tokens.Current().UpperOnOf(["(", "SET", "VALUES", "DEFAULT"]):
585
+ table_name += tokens.Current().Lexeme + ' '
586
+ plain_query += table_name + ' ' + tokens.Current().Lexeme + ' '
587
+ while tokens.MoveNext() and not tokens.Current().UpperEquals("RETURNING"):
588
+ plain_query += tokens.Current().Lexeme + ' '
589
+
590
+ select_query = None
591
+ if not tokens.AtEnd():
592
+ select_query = 'SELECT '
593
+ while tokens.MoveNext():
594
+ select_query += tokens.Current().Lexeme + ' '
595
+ select_query += "FROM " + table_name
596
+ select_query += " WHERE %ID %INLIST ?"
597
+
598
+ return False, plain_query, select_query
599
+
555
600
  def InsertMultiValues(self, query):
556
601
  new_query = ''
557
602
  values_list = []
@@ -580,7 +625,10 @@ class _PreParser(object):
580
625
  break
581
626
  if token.TokenType is TOKEN.CONSTANT:
582
627
  values += '?'
583
- params += [token.Lexeme]
628
+ param = token.Lexeme
629
+ if param.__len__ and param[0] == "'" and param[0] == param[-1]:
630
+ param = param[1: -1]
631
+ params += [param]
584
632
  else:
585
633
  values += token.Lexeme
586
634
  values += ' '
@@ -1047,6 +1095,7 @@ class _PreParser(object):
1047
1095
  nParamIndex += 1
1048
1096
  if nParamIndex == length + 1:
1049
1097
  break
1098
+
1050
1099
  return pOut
1051
1100
 
1052
1101
  # '?' represents a parameter; adds a parameter to p_Parameters if none were provided
@@ -76,6 +76,9 @@ It records the classification of the token as well as retaining the original str
76
76
  def UpperEquals(self, p_str):
77
77
  return self.UpperLexeme == p_str
78
78
 
79
+ def UpperOnOf(self, p_str_list):
80
+ return self.UpperLexeme in p_str_list
81
+
79
82
  def UpperContains(self, p_str):
80
83
  return p_str in self.UpperLexeme
81
84
 
@@ -216,6 +216,9 @@ class LinkedListEnumerator(ITokenEnumerator):
216
216
  def Current(self):
217
217
  return self.m_Current.GetValue()
218
218
 
219
+ def AtEnd(self):
220
+ return self.m_bEOF
221
+
219
222
  def MoveNext(self):
220
223
  if self.m_bEOF:
221
224
  return False
sqlalchemy_iris/base.py CHANGED
@@ -653,6 +653,20 @@ class IRISDDLCompiler(sql.compiler.DDLCompiler):
653
653
  def visit_check_constraint(self, constraint, **kw):
654
654
  pass
655
655
 
656
+ def create_table_constraints(self, table, **kw):
657
+ description = ""
658
+ comment = table.comment
659
+ if comment:
660
+ # hack to keep \r, kind of
661
+ comment = comment.replace('\r', '\n\t')
662
+ literal = self.sql_compiler.render_literal_value(comment, sqltypes.String())
663
+ description = "%DESCRIPTION " + literal
664
+
665
+ constraints = super().create_table_constraints(table, **kw)
666
+ if constraints and description:
667
+ description = ", \n\t" + description
668
+ return constraints + description
669
+
656
670
  def visit_add_constraint(self, create, **kw):
657
671
  if isinstance(create.element, schema.CheckConstraint):
658
672
  raise exc.CompileError("Can't add CHECK constraint")
@@ -701,6 +715,7 @@ class IRISDDLCompiler(sql.compiler.DDLCompiler):
701
715
 
702
716
  comment = column.comment
703
717
  if comment is not None:
718
+ comment = comment.replace('\r', '\n\t')
704
719
  literal = self.sql_compiler.render_literal_value(comment, sqltypes.String())
705
720
  colspec.append("%DESCRIPTION " + literal)
706
721
 
@@ -883,6 +898,12 @@ class IRISDialect(default.DefaultDialect):
883
898
  type_compiler = IRISTypeCompiler
884
899
  execution_ctx_cls = IRISExecutionContext
885
900
 
901
+ insert_returning = True
902
+ insert_executemany_returning = True
903
+ insert_executemany_returning_sort_by_parameter_order = True
904
+ update_executemany_returning = False
905
+ delete_executemany_returning = False
906
+
886
907
  construct_arguments = [
887
908
  (schema.Index, {"include": None}),
888
909
  ]
@@ -1105,6 +1126,38 @@ There are no access to %Dictionary, may be required for some advanced features,
1105
1126
  return "SQLUser"
1106
1127
  return schema
1107
1128
 
1129
+ @reflection.cache
1130
+ def get_table_options(self, connection, table_name, schema=None, **kw):
1131
+ if not self.has_table(connection=connection, table_name=table_name, schema=schema):
1132
+ raise exc.NoSuchTableError(
1133
+ f"{schema}.{table_name}" if schema else table_name
1134
+ ) from None
1135
+ return {}
1136
+
1137
+ @reflection.cache
1138
+ def get_table_comment(self, connection, table_name, schema=None, **kw):
1139
+ if not self.has_table(connection=connection, table_name=table_name, schema=schema):
1140
+ raise exc.NoSuchTableError(
1141
+ f"{schema}.{table_name}" if schema else table_name
1142
+ ) from None
1143
+
1144
+ tables = ischema.tables
1145
+ schema_name = self.get_schema(schema)
1146
+
1147
+ s = sql.select(tables.c.description).where(
1148
+ sql.and_(
1149
+ tables.c.table_schema == str(schema_name),
1150
+ tables.c.table_name == str(table_name),
1151
+ )
1152
+ )
1153
+ comment = connection.execute(s).scalar()
1154
+ if comment:
1155
+ # make it as \r
1156
+ comment = comment.replace(' \t\t\t\t', '\r')
1157
+ # restore \n
1158
+ comment = comment.replace(' \t\t\t', '\n')
1159
+ return {"text": comment}
1160
+
1108
1161
  @reflection.cache
1109
1162
  def get_schema_names(self, connection, **kw):
1110
1163
  s = sql.select(ischema.schemata.c.schema_name).order_by(
@@ -1131,7 +1184,7 @@ There are no access to %Dictionary, may be required for some advanced features,
1131
1184
  return table_names
1132
1185
 
1133
1186
  @reflection.cache
1134
- def get_temp_table_names(self, connection, dblink=None, **kw):
1187
+ def get_temp_table_names(self, connection, **kw):
1135
1188
  tables = ischema.tables
1136
1189
  s = (
1137
1190
  sql.select(tables.c.table_name)
@@ -1598,6 +1651,7 @@ There are no access to %Dictionary, may be required for some advanced features,
1598
1651
  columns.c.column_default,
1599
1652
  columns.c.collation_name,
1600
1653
  columns.c.auto_increment,
1654
+ columns.c.description,
1601
1655
  )
1602
1656
  .select_from(columns)
1603
1657
  .where(
@@ -1650,7 +1704,12 @@ There are no access to %Dictionary, may be required for some advanced features,
1650
1704
  sqlComputeCode = row[property.c.SqlComputeCode]
1651
1705
  calculated = row[property.c.Calculated]
1652
1706
  transient = row[property.c.Transient]
1653
- # description = row[columns.c.description]
1707
+ comment = row[columns.c.description]
1708
+ if comment:
1709
+ # make it as \r
1710
+ comment = comment.replace(' \t\t\t\t', '\r')
1711
+ # restore \n
1712
+ comment = comment.replace(' \t\t\t', '\n')
1654
1713
 
1655
1714
  coltype = self.ischema_names.get(type_, None)
1656
1715
 
@@ -1696,7 +1755,7 @@ There are no access to %Dictionary, may be required for some advanced features,
1696
1755
  "nullable": nullable,
1697
1756
  "default": default,
1698
1757
  "autoincrement": autoincrement,
1699
- # "comment": description,
1758
+ "comment": comment,
1700
1759
  }
1701
1760
  if sqlComputeCode and "set {*} = " in sqlComputeCode.lower():
1702
1761
  sqltext = sqlComputeCode
@@ -44,6 +44,7 @@ tables = Table(
44
44
  Column("TABLE_NAME", String, key="table_name"),
45
45
  Column("TABLE_TYPE", String, key="table_type"),
46
46
  Column("CLASSNAME", String, key="classname"),
47
+ Column("DESCRIPTION", String, key="description"),
47
48
  schema="INFORMATION_SCHEMA",
48
49
  )
49
50
 
@@ -69,7 +70,7 @@ columns = Table(
69
70
  Column("AUTO_INCREMENT", YESNO, key="auto_increment"),
70
71
  Column("UNIQUE_COLUMN", YESNO, key="unique_column"),
71
72
  Column("PRIMARY_KEY", YESNO, key="primary_key"),
72
- Column("DESCIPTION", String, key="desciption"),
73
+ Column("DESCRIPTION", String, key="description"),
73
74
  schema="INFORMATION_SCHEMA",
74
75
  )
75
76
  property_definition = Table(
@@ -58,6 +58,22 @@ class Requirements(SuiteRequirements, AlembicRequirements):
58
58
 
59
59
  return exclusions.open()
60
60
 
61
+ @property
62
+ def reflect_table_options(self):
63
+ return exclusions.open()
64
+
65
+ @property
66
+ def comment_reflection(self):
67
+ return exclusions.open()
68
+
69
+ @property
70
+ def insert_returning(self):
71
+ return exclusions.open()
72
+
73
+ @property
74
+ def unusual_column_name_characters(self):
75
+ return exclusions.open()
76
+
61
77
  @property
62
78
  def computed_columns(self):
63
79
  "Supports computed columns"
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: sqlalchemy-iris
3
- Version: 0.15.5b1
3
+ Version: 0.16.0
4
4
  Summary: InterSystems IRIS for SQLAlchemy
5
5
  Home-page: https://github.com/caretdev/sqlalchemy-iris
6
6
  Maintainer: CaretDev
@@ -23,6 +23,7 @@ Requires-Python: >=3.8
23
23
  Description-Content-Type: text/markdown
24
24
  License-File: LICENSE
25
25
  Requires-Dist: SQLAlchemy>=1.3
26
+ Dynamic: requires-dist
26
27
 
27
28
  sqlalchemy-iris
28
29
  ===
@@ -32,20 +32,20 @@ intersystems_iris/_PythonGateway.py,sha256=miV92LhCqsuBIRpLix1oqFjl5SCw9Df-GMTD6
32
32
  intersystems_iris/_SharedMemorySocket.py,sha256=2iUaS1FdJNSCUEaE-VT0O_dxF6NRwqZSxlyLfwhT9_E,4330
33
33
  intersystems_iris/__init__.py,sha256=Tk1tD28LwvF6X3yXQsJFLE1Bc-PR3gUJWcX5UnNYOdY,1773
34
34
  intersystems_iris/__main__.py,sha256=rCtINTfJcADMAsw1ja-MEO7Q-XekrWthYowzWV8xGIQ,218
35
- intersystems_iris/dbapi/_Column.py,sha256=VCLHLXs3wuGcUa9w_qy7HBFsuGvhmmI3kGYBagQg59U,2535
36
- intersystems_iris/dbapi/_DBAPI.py,sha256=PZWhstxBQMw6X6AFL0IObvTzY9bJEl6u93keCrNGRmA,103103
35
+ intersystems_iris/dbapi/_Column.py,sha256=WgrqlZru9KxHveErKv6qzs8RSTeYlidK9DvuVkjpvIk,2632
36
+ intersystems_iris/dbapi/_DBAPI.py,sha256=UEZmr5pHF6TUqtqZiM91750SNrBP-qQdWtVF2Tcd8f4,104356
37
37
  intersystems_iris/dbapi/_Descriptor.py,sha256=IjyITxvjygDrhpk-0lGhdqQPh91SG6nTb1vi-AqyJNI,1391
38
- intersystems_iris/dbapi/_IRISStream.py,sha256=dGJntWo4HXgM1nUHZl2hA4xHkBFEU2xkoEplVDFWhnA,2115
38
+ intersystems_iris/dbapi/_IRISStream.py,sha256=M3d0SUfE7vyqCxbquSNaRc43J4Xc9i0XGWJnIJzUVVA,2173
39
39
  intersystems_iris/dbapi/_Message.py,sha256=jpLG3HZElqp981iNPFW8UNRO3NbHf6poEv6yywX0Ssw,4076
40
40
  intersystems_iris/dbapi/_Parameter.py,sha256=lvPlQkoLlyEjg5J_a9t2I_6vRDkAav6kN1fGyukH4DY,5293
41
41
  intersystems_iris/dbapi/_ParameterCollection.py,sha256=kcgNiGv2nH5AwuA6LlPpM4dWqhSqRyD3VtwqHqDGRGU,5541
42
- intersystems_iris/dbapi/_ResultSetRow.py,sha256=tEQCttO6L2McLfcP7-PIltIu5L9cNzTI1jZ2IR7tdRg,13777
42
+ intersystems_iris/dbapi/_ResultSetRow.py,sha256=enLGhY7FEZr1VGG-i8vojow2QJxOm-XDM93F1eHtu3U,13777
43
43
  intersystems_iris/dbapi/_SQLType.py,sha256=IlDacXwQzUMWaJ02Zsu2bUfvUC3-5mBx-m6mE0Yp7ts,557
44
44
  intersystems_iris/dbapi/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
45
- intersystems_iris/dbapi/preparser/_PreParser.py,sha256=024w-s3n_JNsk_ISC8avJMK3QEZE3hSuD0Nr8arJdZw,78828
45
+ intersystems_iris/dbapi/preparser/_PreParser.py,sha256=CjvU6uQjIo4t-HvvoxERpc_wZZkqDCM8U6GOFVLWr3c,80925
46
46
  intersystems_iris/dbapi/preparser/_Scanner.py,sha256=xA8rkKsaVba_mAgoXEeliyuMxNKe4vVmmw_klukdf_U,16163
47
- intersystems_iris/dbapi/preparser/_Token.py,sha256=9ZYrjvYJtMApAR7RtYzp32Hcoo3jB_YpG7ECo7p6QHA,2304
48
- intersystems_iris/dbapi/preparser/_TokenList.py,sha256=P74kA3DXxi7imt0mea4LPjCCc_gk50zsYLOCWON_JvA,6770
47
+ intersystems_iris/dbapi/preparser/_Token.py,sha256=6hecLgne6ErgFIF4-dP_CLqsGBBDdlf3tXeQQfdI_uE,2388
48
+ intersystems_iris/dbapi/preparser/_TokenList.py,sha256=ae2kwIJP9-o7BOfaEuuQ5pHWYil7o18Mm40oIEyWIAM,6819
49
49
  intersystems_iris/dbapi/preparser/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
50
50
  intersystems_iris/pex/_BusinessHost.py,sha256=arBAUZetLPhJY2on3BK5BJOZXXjLVJ3gr_VvkncbWDs,4370
51
51
  intersystems_iris/pex/_BusinessOperation.py,sha256=gEyPF6A04si6UqVUY_75ulYzjMBhe337GTkcbK2OGfA,5241
@@ -61,27 +61,21 @@ intersystems_iris/pex/_InboundAdapter.py,sha256=gZlWl7afumamkj8pNbpLyKFSzhaTAiAX
61
61
  intersystems_iris/pex/_Message.py,sha256=Ugaa_lsEYke__pI5kdC7phAuyPQ7rxXUcROJL4cUxVQ,320
62
62
  intersystems_iris/pex/_OutboundAdapter.py,sha256=ao2Ubbta2DcrQGdzDUD_j1Zsk8bvUfcZNKTZkzPTNBU,1628
63
63
  intersystems_iris/pex/__init__.py,sha256=l_I1dpnluWawbFrGMDC0GLHpuHwjbpd-nho8otFX6TE,1379
64
- iris/__init__.py,sha256=i0PK20mjoWOnFPQM0pHF8wMjjmh-5i1xLEduy4r-q9o,2036
65
- iris/__init__.pyi,sha256=HPv6Lot6yZt8XXj3Rb0CrVO9ChDbKVpE3ID1SIqAlwU,9766
66
- iris/_cli.py,sha256=HsmaY22h2a9EofCLcm_CU-ZikQNqFPDlH5xDvLon-Ko,1983
67
- iris/iris_ipm.py,sha256=Q0jcNItjywlqOPZr0hgdTFSeLPNEmB-tcICOI_cXnaY,790
68
- iris/iris_ipm.pyi,sha256=j7CNUZcjeDu5sgeWUZJO_Qi4vQmHh6aD-jPWv8OdoUs,374
69
- iris/iris_utils.py,sha256=kg80O3yRpHIutM-mCyr4xCeTvKWPE-Kai-b6Dxw4vQ4,9882
70
64
  irisnative/_IRISNative.py,sha256=HQ4nBhc8t8_5OtxdMG-kx1aa-T1znf2I8obZOPLOPzg,665
71
65
  irisnative/__init__.py,sha256=6YmvBLQSURsCPKaNg7LK-xpo4ipDjrlhKuwdfdNb3Kg,341
72
66
  sqlalchemy_iris/__init__.py,sha256=2bckDQ0AJJ9DfuAc20VVQNiK-YBhpYJMdo_C0qJwPHQ,1183
73
67
  sqlalchemy_iris/alembic.py,sha256=L58qBIj6HwLmtU6FS5RXKW0gHr1mNTrBSChEllSh1n0,6607
74
- sqlalchemy_iris/base.py,sha256=WdRRnB31W50fWqptTyalgSM7AMA73Qye7sugU6SFuDE,52409
68
+ sqlalchemy_iris/base.py,sha256=xkAYNo6TFIF7f6cvP2h_7TSiEJBbprmYrmpXM0FkWAM,54679
75
69
  sqlalchemy_iris/embedded.py,sha256=5WZ78PIYB_pPyaLrK4E7kHUsGBRiwzYHjsTDiNYHUGg,819
76
- sqlalchemy_iris/information_schema.py,sha256=Ei1gAHXn4fWpvmUzwf-2hGslU458uQXFt1s0r1NAj2Y,6132
70
+ sqlalchemy_iris/information_schema.py,sha256=FUL3z_viGjjOvDA71Mbk5k94dUGcLV4dW1xHxBgM1rk,6188
77
71
  sqlalchemy_iris/iris.py,sha256=Of0Ruc9W2c5ll5sjAy1xRo4tf1m0l_ab0vAdacTv3Yw,276
78
72
  sqlalchemy_iris/irisasync.py,sha256=7Kmso-RGjxQi9Y4x-zQaUk1ylDQ7TDvpvlZh_syJtGw,312
79
73
  sqlalchemy_iris/provision.py,sha256=drorbIgNO770Ws0XiCRXY_sDbQGIy2_zzNK3KYrDetY,198
80
- sqlalchemy_iris/requirements.py,sha256=2MPADPOk3qvXCyr7xXvkl_MfkE8v75Z3yGJO0RbzRog,7560
74
+ sqlalchemy_iris/requirements.py,sha256=X0e_aeO83ucg3iwnX5n_CPUcwmav_SqLbaxj82_-MiU,7901
81
75
  sqlalchemy_iris/types.py,sha256=mtm5wQQuA4pE6WB4EUHzAcpdBCLfZKHrY9qOcdllc7E,11077
82
- sqlalchemy_iris-0.15.5b1.dist-info/LICENSE,sha256=RQmigqltsLq8lfOBc_KwtL0gkODyUCNpU-0ZiZwGlho,1075
83
- sqlalchemy_iris-0.15.5b1.dist-info/METADATA,sha256=smepCH5XDjfRCmh031kqyIKUtgLuFaebLmCqRaSwAvo,2356
84
- sqlalchemy_iris-0.15.5b1.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
85
- sqlalchemy_iris-0.15.5b1.dist-info/entry_points.txt,sha256=zjOwyJPXHsNAeQP0n6l2_pav2U__rTAiS_Bk_IEmSlU,184
86
- sqlalchemy_iris-0.15.5b1.dist-info/top_level.txt,sha256=mjpHqFjekbB1TWr3xI3o4AqN3Spby-_uqyuSSeBDmuw,50
87
- sqlalchemy_iris-0.15.5b1.dist-info/RECORD,,
76
+ sqlalchemy_iris-0.16.0.dist-info/LICENSE,sha256=RQmigqltsLq8lfOBc_KwtL0gkODyUCNpU-0ZiZwGlho,1075
77
+ sqlalchemy_iris-0.16.0.dist-info/METADATA,sha256=dyn6HzzopjhZjG98qhxW0iYRsNWma0yTbAUaByNHtkk,2377
78
+ sqlalchemy_iris-0.16.0.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
79
+ sqlalchemy_iris-0.16.0.dist-info/entry_points.txt,sha256=zjOwyJPXHsNAeQP0n6l2_pav2U__rTAiS_Bk_IEmSlU,184
80
+ sqlalchemy_iris-0.16.0.dist-info/top_level.txt,sha256=QRY18YUXUJrRde4aayj_brj0lr2LBRVZS1ZoVoDFVS0,45
81
+ sqlalchemy_iris-0.16.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.6.0)
2
+ Generator: setuptools (75.8.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,4 +1,3 @@
1
1
  intersystems_iris
2
- iris
3
2
  irisnative
4
3
  sqlalchemy_iris
iris/__init__.py DELETED
@@ -1,66 +0,0 @@
1
- import os
2
- import sys
3
- import importlib
4
-
5
- from .iris_ipm import ipm
6
- from .iris_utils import update_dynalib_path
7
-
8
- # check for install dir in environment
9
- # environment to check is IRISINSTALLDIR
10
- # if not found, raise exception and exit
11
- # ISC_PACKAGE_INSTALLDIR - defined by default in Docker images
12
- installdir = os.environ.get('IRISINSTALLDIR') or os.environ.get('ISC_PACKAGE_INSTALLDIR')
13
- if installdir is None:
14
- raise Exception("""Cannot find InterSystems IRIS installation directory
15
- Please set IRISINSTALLDIR environment variable to the InterSystems IRIS installation directory""")
16
-
17
- __sysversion_info = sys.version_info
18
- __syspath = sys.path
19
- __osname = os.name
20
-
21
- # join the install dir with the bin directory
22
- __syspath.append(os.path.join(installdir, 'bin'))
23
- # also append lib/python
24
- __syspath.append(os.path.join(installdir, 'lib', 'python'))
25
-
26
- # update the dynalib path
27
- update_dynalib_path(os.path.join(installdir, 'bin'))
28
-
29
- # save working directory
30
- __ospath = os.getcwd()
31
-
32
- __irispythonint = None
33
-
34
- if __osname=='nt':
35
- if __sysversion_info.minor==9:
36
- __irispythonint = 'pythonint39'
37
- elif __sysversion_info.minor==10:
38
- __irispythonint = 'pythonint310'
39
- elif __sysversion_info.minor==11:
40
- __irispythonint = 'pythonint311'
41
- elif __sysversion_info.minor==12:
42
- __irispythonint = 'pythonint312'
43
- elif __sysversion_info.minor==13:
44
- __irispythonint = 'pythonint313'
45
- else:
46
- __irispythonint = 'pythonint'
47
-
48
- if __irispythonint is not None:
49
- # equivalent to from pythonint import *
50
- try:
51
- __irispythonintmodule = importlib.import_module(__irispythonint)
52
- except ImportError:
53
- __irispythonint = 'pythonint'
54
- __irispythonintmodule = importlib.import_module(__irispythonint)
55
- globals().update(vars(__irispythonintmodule))
56
-
57
- # restore working directory
58
- os.chdir(__ospath)
59
-
60
- # TODO: Figure out how to hide __syspath and __ospath from anyone that
61
- # imports iris. Tried __all__ but that only applies to this:
62
- # from iris import *
63
-
64
- #
65
- # End-of-file
66
- #
iris/__init__.pyi DELETED
@@ -1,236 +0,0 @@
1
- from __future__ import annotations
2
- from typing import Any
3
- from iris.iris_ipm import ipm
4
- from . import iris_ipm
5
- __all__ = [ 'check_status', 'cls', 'execute', 'gref', 'ipm', 'iris_ipm', 'lock', 'os', 'ref', 'routine', 'sql', 'system', 'tcommit', 'tlevel', 'trollback', 'trollbackone', 'tstart', 'unlock', 'utils']
6
- def check_status(self, status):
7
- """
8
- Raises an exception on an error status, or returns None if no error condition occurs.
9
- Example: iris.check_status(st) checks the status code st to see if it contains an error.
10
- """
11
- def cls(self, class_name):
12
- """
13
- Returns a reference to an InterSystems IRIS class.
14
- Example: iris.cls("%SYSTEM.INetInfo").LocalHostName() calls a method in the class %SYSTEM.INetInfo.
15
- """
16
- def execute(self, statements):
17
- """
18
- execute IRIS statements.
19
- Example: iris.execute("set x="Hello"\nw x,!\n") returns nothing.
20
- """
21
- def gref(self, global_name) -> global_ref:
22
- """
23
- Returns a reference to an InterSystems IRIS global.
24
- Example: g = iris.gref("^foo") sets g to a reference to global ^foo
25
- """
26
- def lock(self, lock_list, timeout_value, locktype):
27
- """
28
- Sets locks, given a list of lock names, an optional timeout value (in seconds), and an optional locktype.
29
- Example: iris.lock(["^foo","^bar"], 30, "S") sets locks "^foo" and "^bar", waiting up to 30 seconds, and using shared locks.
30
- """
31
- def ref(self, value):
32
- """
33
- Creates an iris.ref object with a specified value.
34
- Example: iris.ref("hello") creates an iris.ref object with the value "hello"
35
- """
36
- def routine(self, routine, args):
37
- """
38
- Invokes an InterSystems IRIS routine, optionally at a given tag.
39
- Example: iris.routine("Stop^SystemPerformance", "20211221_160620_test") calls tag Stop in routine ^SystemPerformance.
40
- """
41
- def tcommit(self):
42
- """
43
- Marks a successful end of an InterSystems IRIS transaction.
44
- Example: iris.commit() marks the successful end of a transaction and decrements the nesting level by 1
45
- """
46
- def tlevel(self):
47
- """
48
- Detects whether a transaction is currently in progress and returns the nesting level. Zero means not in a transaction.
49
- Example: iris.tlevel() returns the current transaction nesting level, or zero if not in a transaction
50
- """
51
- def trollback(self):
52
- """
53
- Terminates the current transaction and restores all journaled database values to their values at the start of the transaction.
54
- Example: iris.trollback() rolls back all current transactions in progress and resets the transaction nesting level to 0
55
- """
56
- def trollbackone(self):
57
- """
58
- Rolls back the current level of nested transactions, that is, the one initiated by the most recent tstart().
59
- Example: iris.trollbackone() rolls back the current level of nested transactions and decrements the nesting level by 1
60
- """
61
- def tstart(self):
62
- """
63
- Starts an InterSystems IRIS transaction.
64
- Example: iris.tstart() marks the beginning of a transaction.
65
- """
66
- def unlock(self, lock_list, timout_value, locktype):
67
- """
68
- Removes locks, given a list of lock names, an optional timeout value (in seconds), and an optional locktype.
69
- Example: iris.unlock(["^foo","^bar"], 30, "S") removes locks "^foo" and "^bar", waiting up to 30 seconds, and using shared locks.
70
- """
71
- def utils(self):
72
- """
73
- Returns a reference to the InterSystems IRIS utilities class.
74
- Example: iris.utils().$Job() returns the current job number.
75
- """
76
- # Stubs for the gref object
77
- class global_ref:
78
- def data(self, key):
79
- """
80
- Checks if a node of a global contains data and/or has descendants. The key of the node is passed as a list. Passing a key with the
81
- value None (or an empty list) indicates the root node of the global.\n
82
- You can use data() to inspect a node to see if it contains data before attempting to access that data and possibly encountering an error.
83
- The method returns 0 if the node is undefined (contains no data), 1 if the node is defined (contains data), 10 if the node is undefined
84
- but has descendants, or 11 if the node is defined and has descendants.
85
- """
86
- def get(self, key):
87
- """
88
- Gets the value stored at a node of a global. The key of the node is passed as a list. Passing a key with the value None (or an empty list)
89
- indicates the root node of the global.
90
- """
91
- def getAsBytes(self, key):
92
- """
93
- Gets a string value stored at a node of a global and converts it to the Python bytes data type. The key of the node is passed as a list.
94
- Passing a key with the value None (or an empty list) indicates the root node of the global.
95
- """
96
- def keys(self, key):
97
- """
98
- Returns the keys of a global, starting from a given key. The starting key is passed as a list. Passing an empty list indicates the root node of the global.
99
- """
100
- def kill(self, key):
101
- """
102
- Deletes the node of a global, if it exists. The key of the node is passed as a list. This also deletes any descendants of the node.
103
- Passing a key with the value None (or an empty list) indicates the root node of the global.
104
- """
105
- def order(self, key):
106
- """
107
- Returns the next key in that level of the global, starting from a given key. The starting key is passed as a list.
108
- If no key follows the starting key, order() returns None.
109
- """
110
- def orderiter(self, key):
111
- """
112
- Returns the keys and values of a global, starting from a given key, down to the next leaf node.
113
- The starting key is passed as a list. Passing an empty list indicates the root node of the global.
114
- """
115
- def query(self, key):
116
- """
117
- Traverses a global starting at the specified key, returning each key and value.
118
- The starting key is passed as a list. Passing an empty list indicates the root node of the global.
119
- """
120
- def set(self, key, value):
121
- """
122
- Sets a node in a global to a given value. The key of the node is passed as a list, and value is the value to be stored.
123
- Passing a key with the value None (or an empty list) indicates the root node of the global.
124
- """
125
- # stubs for the sql object
126
- class sql:
127
- """
128
- The sql object provides access to the InterSystems IRIS SQL API.
129
- """
130
- def exec(self, query: str) -> Any:
131
- """
132
- Execute a query
133
- """
134
- def prepare(self, query: str) -> PreparedQuery:
135
- """
136
- Prepare a query
137
- """
138
- class PreparedQuery:
139
- def execute(self, **kwargs) -> Any:
140
- """
141
- Execute a prepared query, you can pass values
142
- """
143
- # stubs for the system object
144
- class system:
145
- """
146
- The system object provides access to the InterSystems IRIS system API.
147
- The following classes are available:
148
- 'DocDB', 'Encryption', 'Error', 'Event', 'Monitor', 'Process', 'Python', 'SQL', 'SYS', 'Security', 'Semaphore', 'Status', 'Util', 'Version'
149
- """
150
- class DocDB:
151
- """
152
- The DocDB class provides access to the InterSystems IRIS Document Database API.
153
- The following methods are available:
154
- 'CreateDatabase', 'DropAllDatabases', 'DropDatabase', 'Exists', 'GetAllDatabases', 'GetDatabase', 'Help'
155
- """
156
- def CreateDatabase(self, name: str, path: str, **kwargs) -> Any:
157
- """
158
- Create a database
159
- """
160
- def DropAllDatabases(self) -> Any:
161
- """
162
- Drop all databases
163
- """
164
- def DropDatabase(self, name: str) -> Any:
165
- """
166
- Drop a database
167
- """
168
- def Exists(self, name: str) -> Any:
169
- """
170
- Check if a database exists
171
- """
172
- def GetAllDatabases(self) -> Any:
173
- """
174
- Get all databases
175
- """
176
- def GetDatabase(self, name: str) -> Any:
177
- """
178
- Get a database
179
- """
180
- def Help(self) -> Any:
181
- """
182
- Get help
183
- """
184
- def Encryption(self) -> Any:
185
- """
186
- The Encryption class provides access to the InterSystems IRIS Encryption API.
187
- """
188
- def Error(self) -> Any:
189
- """
190
- The Error class provides access to the InterSystems IRIS Error API.
191
- """
192
- def Event(self) -> Any:
193
- """
194
- The Event class provides access to the InterSystems IRIS Event API.
195
- """
196
- def Monitor(self) -> Any:
197
- """
198
- The Monitor class provides access to the InterSystems IRIS Monitor API.
199
- """
200
- def Process(self) -> Any:
201
- """
202
- The Process class provides access to the InterSystems IRIS Process API.
203
- """
204
- def Python(self) -> Any:
205
- """
206
- The Python class provides access to the InterSystems IRIS Python API.
207
- """
208
- def SQL(self) -> Any:
209
- """
210
- The SQL class provides access to the InterSystems IRIS SQL API.
211
- """
212
- def SYS(self) -> Any:
213
- """
214
- The SYS class provides access to the InterSystems IRIS SYS API.
215
- """
216
- def Security(self) -> Any:
217
- """
218
- The Security class provides access to the InterSystems IRIS Security API.
219
- """
220
- def Semaphore(self) -> Any:
221
- """
222
- The Semaphore class provides access to the InterSystems IRIS Semaphore API.
223
- """
224
- def Status(self) -> Any:
225
- """
226
- The Status class provides access to the InterSystems IRIS Status API.
227
- """
228
- def Util(self) -> Any:
229
- """
230
- The Util class provides access to the InterSystems IRIS Util API.
231
- """
232
- def Version(self) -> Any:
233
- """
234
- The Version class provides access to the InterSystems IRIS Version API.
235
- """
236
-
iris/_cli.py DELETED
@@ -1,75 +0,0 @@
1
- import os
2
- import sys
3
- import argparse
4
- import logging
5
-
6
- from . import iris_utils
7
- import iris
8
-
9
- logging.basicConfig(level=logging.INFO)
10
-
11
- VENV_BACKUP_GREF = "^Venv.BackUp"
12
-
13
- def bind():
14
- parser = argparse.ArgumentParser()
15
- parser.add_argument("--namespace", default="")
16
- args = parser.parse_args()
17
-
18
- iris_gref = iris.gref(VENV_BACKUP_GREF)
19
-
20
- path = ""
21
-
22
- libpython = iris_utils.find_libpython()
23
- if not libpython:
24
- logging.error("libpython not found")
25
- raise RuntimeError("libpython not found")
26
-
27
- iris.system.Process.SetNamespace("%SYS")
28
-
29
- config = iris.cls("Config.config").Open()
30
-
31
- # Set the new libpython path
32
- iris_gref["PythonRuntimeLibrary"] = config.PythonRuntimeLibrary
33
-
34
- config.PythonRuntimeLibrary = libpython
35
-
36
- if "VIRTUAL_ENV" in os.environ:
37
- # we are not in a virtual environment
38
- path = os.path.join(os.environ["VIRTUAL_ENV"], "lib", "python" + sys.version[:4], "site-packages")
39
-
40
- iris_gref["PythonPath"] = config.PythonPath
41
-
42
- config.PythonPath = path
43
-
44
- config._Save()
45
-
46
- log_config_changes(libpython, path)
47
-
48
- def unbind():
49
- iris.system.Process.SetNamespace("%SYS")
50
- config = iris.cls("Config.config").Open()
51
-
52
- iris_gref = iris.gref(VENV_BACKUP_GREF)
53
-
54
- if iris_gref["PythonRuntimeLibrary"]:
55
- config.PythonRuntimeLibrary = iris_gref["PythonRuntimeLibrary"]
56
- else:
57
- config.PythonRuntimeLibrary = ""
58
-
59
- if iris_gref["PythonPath"]:
60
- config.PythonPath = iris_gref["PythonPath"]
61
- else:
62
- config.PythonPath = ""
63
-
64
- config._Save()
65
-
66
- del iris_gref["PythonRuntimeLibrary"]
67
- del iris_gref["PythonPath"]
68
- del iris_gref[None]
69
-
70
- log_config_changes(config.PythonRuntimeLibrary, config.PythonPath)
71
-
72
- def log_config_changes(libpython, path):
73
- logging.info("PythonRuntimeLibrary path set to %s", libpython)
74
- logging.info("PythonPath set to %s", path)
75
- logging.info("To iris instance %s", iris.cls("%SYS.System").GetUniqueInstanceName())
iris/iris_ipm.py DELETED
@@ -1,40 +0,0 @@
1
- def ipm(cmd, *args):
2
- """
3
- Executes shell command with IPM:
4
- Parameters
5
- ----------
6
- cmd : str
7
- The command to execute
8
- Examples
9
- --------
10
- `ipm('help')`
11
- `ipm('load /home/irisowner/dev -v')`
12
- `ipm('install webterminal')`
13
- """
14
-
15
- import multiprocessing
16
- import iris
17
-
18
- def shell(cmd, status):
19
-
20
-
21
- status.put(True)
22
-
23
- res = iris.cls("%ZPM.PackageManager").Shell(cmd)
24
- print('')
25
- if res != 1:
26
- status.get()
27
- status.put(False)
28
-
29
- manager = multiprocessing.Manager()
30
- status = manager.Queue()
31
- process = multiprocessing.Process(
32
- target=shell,
33
- args=(
34
- cmd,
35
- status,
36
- ),
37
- )
38
- process.start()
39
- process.join()
40
- return status.get()
iris/iris_ipm.pyi DELETED
@@ -1,17 +0,0 @@
1
- from __future__ import annotations
2
- __all__ = ['ipm']
3
- def ipm(cmd, *args):
4
- """
5
-
6
- Executes shell command with IPM:
7
- Parameters
8
- ----------
9
- cmd : str
10
- The command to execute
11
- Examples
12
- --------
13
- `ipm('help')`
14
- `ipm('load /home/irisowner/dev -v')`
15
- `ipm('install webterminal')`
16
-
17
- """
iris/iris_utils.py DELETED
@@ -1,336 +0,0 @@
1
- """
2
- Locate libpython associated with this Python executable.
3
- """
4
-
5
- # License
6
- #
7
- # Copyright 2018, Takafumi Arakaki
8
- #
9
- # Permission is hereby granted, free of charge, to any person obtaining
10
- # a copy of this software and associated documentation files (the
11
- # "Software"), to deal in the Software without restriction, including
12
- # without limitation the rights to use, copy, modify, merge, publish,
13
- # distribute, sublicense, and/or sell copies of the Software, and to
14
- # permit persons to whom the Software is furnished to do so, subject to
15
- # the following conditions:
16
- #
17
- # The above copyright notice and this permission notice shall be
18
- # included in all copies or substantial portions of the Software.
19
- #
20
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21
- # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22
- # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23
- # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24
- # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25
- # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26
- # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27
-
28
- from __future__ import print_function, absolute_import
29
-
30
- from logging import getLogger
31
- import ctypes.util
32
- import functools
33
- import os
34
- import sys
35
- import sysconfig
36
- import re
37
-
38
- logger = getLogger("find_libpython")
39
-
40
- is_windows = os.name == "nt"
41
- is_apple = sys.platform == "darwin"
42
-
43
- SHLIB_SUFFIX = sysconfig.get_config_var("SHLIB_SUFFIX")
44
- if SHLIB_SUFFIX is None:
45
- if is_windows:
46
- SHLIB_SUFFIX = ".dll"
47
- else:
48
- SHLIB_SUFFIX = ".so"
49
- if is_apple:
50
- # sysconfig.get_config_var("SHLIB_SUFFIX") can be ".so" in macOS.
51
- # Let's not use the value from sysconfig.
52
- SHLIB_SUFFIX = ".dylib"
53
-
54
-
55
- def linked_libpython():
56
- """
57
- Find the linked libpython using dladdr (in *nix).
58
-
59
- Calling this in Windows always return `None` at the moment.
60
-
61
- Returns
62
- -------
63
- path : str or None
64
- A path to linked libpython. Return `None` if statically linked.
65
- """
66
- if is_windows:
67
- return None
68
- return _linked_libpython_unix()
69
-
70
-
71
- class Dl_info(ctypes.Structure):
72
- _fields_ = [
73
- ("dli_fname", ctypes.c_char_p),
74
- ("dli_fbase", ctypes.c_void_p),
75
- ("dli_sname", ctypes.c_char_p),
76
- ("dli_saddr", ctypes.c_void_p),
77
- ]
78
-
79
-
80
- def _linked_libpython_unix():
81
- libdl = ctypes.CDLL(ctypes.util.find_library("dl"))
82
- libdl.dladdr.argtypes = [ctypes.c_void_p, ctypes.POINTER(Dl_info)]
83
- libdl.dladdr.restype = ctypes.c_int
84
-
85
- dlinfo = Dl_info()
86
- retcode = libdl.dladdr(
87
- ctypes.cast(ctypes.pythonapi.Py_GetVersion, ctypes.c_void_p),
88
- ctypes.pointer(dlinfo))
89
- if retcode == 0: # means error
90
- return None
91
- path = os.path.realpath(dlinfo.dli_fname.decode())
92
- if path == os.path.realpath(sys.executable):
93
- return None
94
- return path
95
-
96
-
97
- def library_name(name, suffix=SHLIB_SUFFIX, is_windows=is_windows):
98
- """
99
- Convert a file basename `name` to a library name (no "lib" and ".so" etc.)
100
-
101
- >>> library_name("libpython3.7m.so") # doctest: +SKIP
102
- 'python3.7m'
103
- >>> library_name("libpython3.7m.so", suffix=".so", is_windows=False)
104
- 'python3.7m'
105
- >>> library_name("libpython3.7m.dylib", suffix=".dylib", is_windows=False)
106
- 'python3.7m'
107
- >>> library_name("python37.dll", suffix=".dll", is_windows=True)
108
- 'python37'
109
- """
110
- if not is_windows and name.startswith("lib"):
111
- name = name[len("lib"):]
112
- if suffix and name.endswith(suffix):
113
- name = name[:-len(suffix)]
114
- return name
115
-
116
-
117
- def append_truthy(list, item):
118
- if item:
119
- list.append(item)
120
-
121
-
122
- def uniquifying(items):
123
- """
124
- Yield items while excluding the duplicates and preserving the order.
125
-
126
- >>> list(uniquifying([1, 2, 1, 2, 3]))
127
- [1, 2, 3]
128
- """
129
- seen = set()
130
- for x in items:
131
- if x not in seen:
132
- yield x
133
- seen.add(x)
134
-
135
-
136
- def uniquified(func):
137
- """ Wrap iterator returned from `func` by `uniquifying`. """
138
- @functools.wraps(func)
139
- def wrapper(*args, **kwds):
140
- return uniquifying(func(*args, **kwds))
141
- return wrapper
142
-
143
-
144
- @uniquified
145
- def candidate_names(suffix=SHLIB_SUFFIX):
146
- """
147
- Iterate over candidate file names of libpython.
148
-
149
- Yields
150
- ------
151
- name : str
152
- Candidate name libpython.
153
- """
154
- LDLIBRARY = sysconfig.get_config_var("LDLIBRARY")
155
- if LDLIBRARY:
156
- yield LDLIBRARY
157
-
158
- LIBRARY = sysconfig.get_config_var("LIBRARY")
159
- if LIBRARY:
160
- yield os.path.splitext(LIBRARY)[0] + suffix
161
-
162
- dlprefix = "" if is_windows else "lib"
163
- sysdata = dict(
164
- v=sys.version_info,
165
- # VERSION is X.Y in Linux/macOS and XY in Windows:
166
- VERSION=(sysconfig.get_config_var("VERSION") or
167
- "{v.major}.{v.minor}".format(v=sys.version_info)),
168
- ABIFLAGS=(sysconfig.get_config_var("ABIFLAGS") or
169
- sysconfig.get_config_var("abiflags") or ""),
170
- )
171
-
172
- for stem in [
173
- "python{VERSION}{ABIFLAGS}".format(**sysdata),
174
- "python{VERSION}".format(**sysdata),
175
- "python{v.major}".format(**sysdata),
176
- "python",
177
- ]:
178
- yield dlprefix + stem + suffix
179
-
180
-
181
-
182
- @uniquified
183
- def candidate_paths(suffix=SHLIB_SUFFIX):
184
- """
185
- Iterate over candidate paths of libpython.
186
-
187
- Yields
188
- ------
189
- path : str or None
190
- Candidate path to libpython. The path may not be a fullpath
191
- and may not exist.
192
- """
193
-
194
- yield linked_libpython()
195
-
196
- # List candidates for directories in which libpython may exist
197
- lib_dirs = []
198
- append_truthy(lib_dirs, sysconfig.get_config_var('LIBPL'))
199
- append_truthy(lib_dirs, sysconfig.get_config_var('srcdir'))
200
- append_truthy(lib_dirs, sysconfig.get_config_var("LIBDIR"))
201
-
202
- # LIBPL seems to be the right config_var to use. It is the one
203
- # used in python-config when shared library is not enabled:
204
- # https://github.com/python/cpython/blob/v3.7.0/Misc/python-config.in#L55-L57
205
- #
206
- # But we try other places just in case.
207
-
208
- if is_windows:
209
- lib_dirs.append(os.path.join(os.path.dirname(sys.executable)))
210
- else:
211
- lib_dirs.append(os.path.join(
212
- os.path.dirname(os.path.dirname(sys.executable)),
213
- "lib"))
214
-
215
- # For macOS:
216
- append_truthy(lib_dirs, sysconfig.get_config_var("PYTHONFRAMEWORKPREFIX"))
217
-
218
- lib_dirs.append(sys.exec_prefix)
219
- lib_dirs.append(os.path.join(sys.exec_prefix, "lib"))
220
-
221
- lib_basenames = list(candidate_names(suffix=suffix))
222
-
223
- for directory in lib_dirs:
224
- for basename in lib_basenames:
225
- yield os.path.join(directory, basename)
226
-
227
- # In macOS and Windows, ctypes.util.find_library returns a full path:
228
- for basename in lib_basenames:
229
- yield ctypes.util.find_library(library_name(basename))
230
-
231
- # Possibly useful links:
232
- # * https://packages.ubuntu.com/bionic/amd64/libpython3.6/filelist
233
- # * https://github.com/Valloric/ycmd/issues/518
234
- # * https://github.com/Valloric/ycmd/pull/519
235
-
236
-
237
- def normalize_path(path, suffix=SHLIB_SUFFIX, is_apple=is_apple):
238
- """
239
- Normalize shared library `path` to a real path.
240
-
241
- If `path` is not a full path, `None` is returned. If `path` does
242
- not exists, append `SHLIB_SUFFIX` and check if it exists.
243
- Finally, the path is canonicalized by following the symlinks.
244
-
245
- Parameters
246
- ----------
247
- path : str ot None
248
- A candidate path to a shared library.
249
- """
250
- if not path:
251
- return None
252
- if not os.path.isabs(path):
253
- return None
254
- if os.path.exists(path):
255
- return os.path.realpath(path)
256
- if os.path.exists(path + suffix):
257
- return os.path.realpath(path + suffix)
258
- if is_apple:
259
- return normalize_path(_remove_suffix_apple(path),
260
- suffix=".so", is_apple=False)
261
- return None
262
-
263
-
264
- def _remove_suffix_apple(path):
265
- """
266
- Strip off .so or .dylib.
267
-
268
- >>> _remove_suffix_apple("libpython.so")
269
- 'libpython'
270
- >>> _remove_suffix_apple("libpython.dylib")
271
- 'libpython'
272
- >>> _remove_suffix_apple("libpython3.7")
273
- 'libpython3.7'
274
- """
275
- if path.endswith(".dylib"):
276
- return path[:-len(".dylib")]
277
- if path.endswith(".so"):
278
- return path[:-len(".so")]
279
- return path
280
-
281
-
282
- @uniquified
283
- def finding_libpython():
284
- """
285
- Iterate over existing libpython paths.
286
-
287
- The first item is likely to be the best one.
288
-
289
- Yields
290
- ------
291
- path : str
292
- Existing path to a libpython.
293
- """
294
- logger.debug("is_windows = %s", is_windows)
295
- logger.debug("is_apple = %s", is_apple)
296
- for path in candidate_paths():
297
- logger.debug("Candidate: %s", path)
298
- normalized = normalize_path(path)
299
- if normalized:
300
- logger.debug("Found: %s", normalized)
301
- yield normalized
302
- else:
303
- logger.debug("Not found.")
304
-
305
-
306
- def find_libpython():
307
- """
308
- Return a path (`str`) to libpython or `None` if not found.
309
-
310
- Parameters
311
- ----------
312
- path : str or None
313
- Existing path to the (supposedly) correct libpython.
314
- """
315
- for path in finding_libpython():
316
- return os.path.realpath(path)
317
-
318
- def update_dynalib_path(dynalib_path):
319
- # Determine the environment variable based on the operating system
320
- env_var = 'PATH'
321
- if not sys.platform.startswith('win'):
322
- if sys.platform == 'darwin':
323
- env_var = 'DYLD_LIBRARY_PATH'
324
- else:
325
- env_var = 'LD_LIBRARY_PATH'
326
-
327
- # Get the current value of the environment variable
328
- current_paths = os.environ.get(env_var, '')
329
-
330
- # Update the environment variable by appending the dynalib path
331
- # Note: You can prepend instead by reversing the order in the join
332
- new_paths = f"{current_paths}:{dynalib_path}" if current_paths else dynalib_path
333
-
334
- # Update the environment variable
335
- os.environ[env_var] = new_paths
336
-