tableauhyperapi 0.0.19484__py3-none-macosx_13_0_arm64.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 (42) hide show
  1. tableauhyperapi/__init__.py +162 -0
  2. tableauhyperapi/bin/hyper/hyperd +0 -0
  3. tableauhyperapi/bin/libtableauhyperapi.dylib +0 -0
  4. tableauhyperapi/catalog.py +173 -0
  5. tableauhyperapi/connection.py +312 -0
  6. tableauhyperapi/databasename.py +85 -0
  7. tableauhyperapi/date.py +85 -0
  8. tableauhyperapi/endpoint.py +25 -0
  9. tableauhyperapi/hyperexception.py +128 -0
  10. tableauhyperapi/hyperprocess.py +155 -0
  11. tableauhyperapi/hyperserviceversion.py +42 -0
  12. tableauhyperapi/impl/__init__.py +0 -0
  13. tableauhyperapi/impl/cdef.py +36 -0
  14. tableauhyperapi/impl/cdef_compiled.py +12 -0
  15. tableauhyperapi/impl/converter.py +36 -0
  16. tableauhyperapi/impl/dll.py +18 -0
  17. tableauhyperapi/impl/dllutil.py +231 -0
  18. tableauhyperapi/impl/hapi.py +276 -0
  19. tableauhyperapi/impl/immutablelistwrapper.py +35 -0
  20. tableauhyperapi/impl/lib_h.py +358 -0
  21. tableauhyperapi/impl/schemaconverter.py +48 -0
  22. tableauhyperapi/impl/util.py +86 -0
  23. tableauhyperapi/impl/version.py +2 -0
  24. tableauhyperapi/inserter.py +719 -0
  25. tableauhyperapi/interval.py +220 -0
  26. tableauhyperapi/name.py +189 -0
  27. tableauhyperapi/result.py +498 -0
  28. tableauhyperapi/resultschema.py +93 -0
  29. tableauhyperapi/schemaname.py +116 -0
  30. tableauhyperapi/sql.py +50 -0
  31. tableauhyperapi/sqltype.py +346 -0
  32. tableauhyperapi/tabledefinition.py +254 -0
  33. tableauhyperapi/tablename.py +129 -0
  34. tableauhyperapi/timestamp.py +310 -0
  35. tableauhyperapi/warning.py +3 -0
  36. tableauhyperapi-0.0.19484.dist-info/HYPER_API_OSS_disclosure.txt +33 -0
  37. tableauhyperapi-0.0.19484.dist-info/LICENSE +70 -0
  38. tableauhyperapi-0.0.19484.dist-info/METADATA +28 -0
  39. tableauhyperapi-0.0.19484.dist-info/NOTICES.txt +3 -0
  40. tableauhyperapi-0.0.19484.dist-info/RECORD +42 -0
  41. tableauhyperapi-0.0.19484.dist-info/WHEEL +5 -0
  42. tableauhyperapi-0.0.19484.dist-info/top_level.txt +2 -0
@@ -0,0 +1,162 @@
1
+ """
2
+ tableauhyperapi - a Hyper client library.
3
+
4
+ This library allows spawning a local Hyper server instance, connecting to a Hyper server, running queries and commands,
5
+ and writing data into database tables.
6
+
7
+ tableauhyperapi classes are implemented in submodules of the tableauhyperapi package, however those submodules are
8
+ considered a private implementation detail, and there are no stability guarantees for them. Only symbols exported
9
+ from the top-level tableauhyperapi package constitute the stable public API.
10
+
11
+ Sample usage
12
+ ------------
13
+
14
+ Create a database and push some data into it:
15
+
16
+ .. testsetup:: tableauhyperapi.__init__
17
+
18
+ import os
19
+ if os.path.exists('mydb.hyper'):
20
+ os.remove('mydb.hyper')
21
+
22
+ .. testcode:: tableauhyperapi.__init__
23
+
24
+ from tableauhyperapi import HyperProcess, Connection, TableDefinition, SqlType, Telemetry, Inserter, CreateMode
25
+
26
+ # Start a new private local Hyper instance
27
+ with HyperProcess(Telemetry.SEND_USAGE_DATA_TO_TABLEAU, 'myapp') as hyper:
28
+ # Create the extract, replace it if it already exists
29
+ with Connection(hyper.endpoint, 'mydb.hyper', CreateMode.CREATE_AND_REPLACE) as connection:
30
+ schema = TableDefinition('foo', [
31
+ TableDefinition.Column('a', SqlType.text()),
32
+ TableDefinition.Column('b', SqlType.big_int()),
33
+ ])
34
+ connection.catalog.create_table(schema)
35
+ with Inserter(connection, schema) as inserter:
36
+ inserter.add_rows([
37
+ ['x', 1],
38
+ ['y', 2],
39
+ ])
40
+ inserter.execute()
41
+
42
+ Connect to an existing extract and append data to a table in it:
43
+
44
+ .. testsetup:: tableauhyperapi.__init__2
45
+
46
+ from tableauhyperapi import *
47
+ hyper = HyperProcess(Telemetry.SEND_USAGE_DATA_TO_TABLEAU, 'myapp')
48
+
49
+ .. testcode:: tableauhyperapi.__init__2
50
+
51
+ with Connection(hyper.endpoint, 'mydb.hyper') as connection:
52
+ with Inserter(connection, 'foo') as inserter:
53
+ inserter.add_row(['z', 3])
54
+ inserter.execute()
55
+
56
+ .. testcleanup:: tableauhyperapi.__init__2
57
+
58
+ hyper.close()
59
+
60
+ Run some queries:
61
+
62
+ .. testsetup:: tableauhyperapi.__init__3
63
+
64
+ from tableauhyperapi import *
65
+ hyper = HyperProcess(Telemetry.SEND_USAGE_DATA_TO_TABLEAU, 'myapp')
66
+ connection = Connection(hyper.endpoint, 'mydb.hyper')
67
+
68
+ .. testcode:: tableauhyperapi.__init__3
69
+
70
+ with connection.execute_query('SELECT * FROM foo') as result:
71
+ rows = list(result)
72
+ assert sorted(rows) == [['x', 1], ['y', 2], ['z', 3]]
73
+
74
+ top_a = connection.execute_scalar_query('SELECT MAX(b) FROM foo')
75
+ assert top_a == 3
76
+
77
+ connection.execute_command('DROP TABLE foo')
78
+
79
+ .. testcleanup:: tableauhyperapi.__init__3
80
+
81
+ connection.close()
82
+ hyper.close()
83
+ import os
84
+ os.remove('mydb.hyper')
85
+
86
+
87
+ API Reference
88
+ -------------
89
+
90
+ .. autosummary::
91
+ :nosignatures:
92
+
93
+ tableauhyperapi.HyperException
94
+ tableauhyperapi.HyperProcess
95
+ tableauhyperapi.Connection
96
+ tableauhyperapi.Endpoint
97
+ tableauhyperapi.Catalog
98
+ tableauhyperapi.Inserter
99
+ tableauhyperapi.TableDefinition
100
+ tableauhyperapi.Persistence
101
+ tableauhyperapi.Result
102
+ tableauhyperapi.ResultSchema
103
+ tableauhyperapi.TypeTag
104
+ tableauhyperapi.SqlType
105
+ tableauhyperapi.Name
106
+ tableauhyperapi.DatabaseName
107
+ tableauhyperapi.SchemaName
108
+ tableauhyperapi.TableName
109
+ tableauhyperapi.Date
110
+ tableauhyperapi.Timestamp
111
+ tableauhyperapi.Interval
112
+ tableauhyperapi.HyperServiceVersion
113
+
114
+ """
115
+
116
+ from .catalog import Catalog
117
+ from .connection import Connection, CreateMode
118
+ from .databasename import DatabaseName
119
+ from .date import Date
120
+ from .endpoint import Endpoint
121
+ from .hyperexception import HyperException
122
+ from .hyperprocess import Telemetry, HyperProcess
123
+ from .hyperserviceversion import HyperServiceVersion
124
+ from .inserter import Inserter
125
+ from .interval import Interval
126
+ from .name import Name
127
+ from .result import Result
128
+ from .resultschema import ResultSchema
129
+ from .schemaname import SchemaName
130
+ from .sql import escape_name, escape_string_literal
131
+ from .sqltype import SqlType, TypeTag
132
+ from .tabledefinition import Nullability, Persistence, TableDefinition
133
+ from .tablename import TableName
134
+ from .timestamp import Timestamp
135
+ from .warning import UnclosedObjectWarning
136
+
137
+ NULLABLE = Nullability.NULLABLE
138
+ NOT_NULLABLE = Nullability.NOT_NULLABLE
139
+ PERMANENT = Persistence.PERMANENT
140
+ TEMPORARY = Persistence.TEMPORARY
141
+
142
+ __all__ = [
143
+ 'HyperException',
144
+ 'HyperProcess', 'Telemetry', 'Connection', 'Endpoint', 'CreateMode', 'Catalog',
145
+ 'Result', 'ResultSchema', 'Inserter', 'Persistence', 'Nullability', 'SqlType', 'TypeTag',
146
+ 'Name', 'SchemaName', 'TableName', 'DatabaseName', 'TableDefinition',
147
+ 'escape_name', 'escape_string_literal',
148
+ 'Date', 'Timestamp', 'Interval', 'UnclosedObjectWarning',
149
+ 'NULLABLE', 'NOT_NULLABLE',
150
+ 'PERMANENT', 'TEMPORARY', 'HyperServiceVersion'
151
+ ]
152
+
153
+ try:
154
+ from .impl import version
155
+ VERSION = version.VERSION
156
+ """ Version number of the library as a tuple `(major, minor, micro)`. """
157
+ __version__ = version.__version__
158
+ """ PEP-396-compliant version number of the library. """
159
+ except ImportError:
160
+ # impl/version.py is a generated file, use all zeros when running from the source directory.
161
+ VERSION = (0, 0, 0)
162
+ __version__ = '0.0.0'
Binary file
@@ -0,0 +1,173 @@
1
+ from pathlib import PurePath
2
+ from typing import Union, Optional, List
3
+
4
+ from .connection import Connection
5
+ from .tablename import Name, SchemaName, TableName, DatabaseName
6
+ from .tabledefinition import TableDefinition
7
+ from .impl import hapi
8
+ from .impl.dll import ffi
9
+ from .impl.dllutil import Error, InteropUtil, NativeTableDefinition
10
+ from .impl.schemaconverter import SchemaConverter
11
+
12
+
13
+ class Catalog:
14
+ """
15
+ The class which is responsible for querying and manipulating metadata.
16
+
17
+ Do not create instances of this class, use :any:`Connection.catalog` instead.
18
+ """
19
+
20
+ def __init__(self, connection: Connection):
21
+ self.__connection = connection
22
+
23
+ @property
24
+ def connection(self) -> Connection:
25
+ """ Gets the underlying connection. """
26
+ return self.__connection
27
+
28
+ @property
29
+ def __cdata(self):
30
+ return self.__connection._cdata
31
+
32
+ def has_table(self, name: Union[TableName, Name, str]) -> bool:
33
+ """ Does a table with this name exist? """
34
+ p_exists = ffi.new('bool*')
35
+ database_name, schema_name, table_name = TableName(name)._unescaped_triple
36
+ Error.check(hapi.hyper_has_table(self.__cdata,
37
+ InteropUtil.string_to_char_p(database_name),
38
+ InteropUtil.string_to_char_p(schema_name),
39
+ InteropUtil.string_to_char_p(table_name),
40
+ p_exists))
41
+ return p_exists[0] != 0
42
+
43
+ def get_table_definition(self, name: Union[TableName, Name, str]) -> TableDefinition:
44
+ """ Gets a table definition. Raises an exception if the table does not exist. """
45
+ database_name, schema_name, table_name = TableName(name)._unescaped_triple
46
+ pp_table_def = ffi.new('hyper_table_definition_t**')
47
+ Error.check(hapi.hyper_get_table_definition(self.__cdata,
48
+ InteropUtil.string_to_char_p(database_name),
49
+ InteropUtil.string_to_char_p(schema_name),
50
+ InteropUtil.string_to_char_p(table_name),
51
+ pp_table_def))
52
+ native = NativeTableDefinition(pp_table_def[0])
53
+ return SchemaConverter.table_definition_from_native(native)
54
+
55
+ def __create_table(self, table_definition: TableDefinition, fail_if_exists: bool):
56
+ native_table_def = SchemaConverter.table_definition_to_native(table_definition)
57
+ Error.check(hapi.hyper_create_table(self.__cdata, native_table_def.cdata, fail_if_exists))
58
+
59
+ def create_table(self, table_definition: TableDefinition):
60
+ """
61
+ Creates a table. Raise an exception if the table already exists.
62
+
63
+ :param table_definition: the table definition.
64
+ """
65
+ self.__create_table(table_definition, True)
66
+
67
+ def create_table_if_not_exists(self, table_definition: TableDefinition):
68
+ """
69
+ Creates a table if it does not already exist, otherwise does nothing.
70
+
71
+ :param table_definition: the table definition.
72
+ """
73
+ self.__create_table(table_definition, False)
74
+
75
+ def get_schema_names(self, database: Union[DatabaseName, Name, str] = None) -> List[SchemaName]:
76
+ """
77
+ Gets the names of all schemas of the database specified by the database name, or of the first database in
78
+ the search path if the name is not specified.
79
+ """
80
+ database_name = DatabaseName(database)._unescaped if database else None
81
+ pp_names = ffi.new('hyper_string_list_t**')
82
+ Error.check(hapi.hyper_get_schema_names(self.__cdata,
83
+ InteropUtil.string_to_char_p(database_name),
84
+ pp_names))
85
+ return [SchemaName(database, name) for name in InteropUtil.convert_and_free_string_list(pp_names)]
86
+
87
+ def get_table_names(self, schema: Union[SchemaName, Name, str]) -> List[TableName]:
88
+ """ Gets the names of all tables in the specified schema. """
89
+ database_name, schema_name = SchemaName(schema)._unescaped_double
90
+ pp_names = ffi.new('hyper_string_list_t**')
91
+ Error.check(hapi.hyper_get_table_names(self.__cdata,
92
+ InteropUtil.string_to_char_p(database_name),
93
+ InteropUtil.string_to_char_p(schema_name),
94
+ pp_names))
95
+ return [TableName(schema, name) for name in InteropUtil.convert_and_free_string_list(pp_names)]
96
+
97
+ def __create_schema(self, schema: Union[str, Name, SchemaName], fail_if_exists: bool):
98
+ schema = SchemaName(schema)
99
+ db_name, schema_name = schema._unescaped_double
100
+ Error.check(hapi.hyper_create_schema(self.__cdata,
101
+ InteropUtil.string_to_char_p(db_name),
102
+ InteropUtil.string_to_char_p(schema_name),
103
+ fail_if_exists))
104
+
105
+ def create_schema(self, schema: Union[str, Name, SchemaName]):
106
+ """
107
+ Creates a new schema with the given name. The schema must not already exist.
108
+
109
+ :param schema: the name of the schema.
110
+ """
111
+ self.__create_schema(schema, True)
112
+
113
+ def create_schema_if_not_exists(self, schema: Union[str, Name, SchemaName]):
114
+ """
115
+ Creates a new schema with the given name if it does not already exist, otherwise does nothing.
116
+
117
+ :param schema: the name of the schema.
118
+ """
119
+ self.__create_schema(schema, False)
120
+
121
+ def create_database(self, database_path: Union[str, PurePath]):
122
+ """
123
+ Creates a new database at the given path. The file must not already exist. It does not attach the database
124
+ to the current connection.
125
+
126
+ :param database_path: path to the database file.
127
+ """
128
+ Error.check(hapi.hyper_create_database(self.__cdata,
129
+ InteropUtil.string_to_char_p(str(database_path)),
130
+ True))
131
+
132
+ def create_database_if_not_exists(self, database_path: Union[str, PurePath]):
133
+ """
134
+ Creates a new database at the given path if it does not already exist, otherwise does nothing. It does not
135
+ attach the database to the current connection.
136
+
137
+ Note: This method raises an exception if a file that is not a hyper database exists at the given path.
138
+
139
+ :param database_path: path to the database file.
140
+ """
141
+ Error.check(hapi.hyper_create_database(self.__cdata,
142
+ InteropUtil.string_to_char_p(str(database_path)),
143
+ False))
144
+
145
+ def attach_database(self, database_path: Union[str, PurePath],
146
+ alias: Optional[Union[str, Name, DatabaseName]] = None):
147
+ """ Attaches a database to the underlying connection. """
148
+ db_path = database_path if isinstance(database_path, PurePath) else PurePath(database_path)
149
+
150
+ if not alias:
151
+ alias = db_path.stem
152
+ alias = DatabaseName(alias)
153
+
154
+ Error.check(hapi.hyper_attach_database(self.__cdata,
155
+ InteropUtil.string_to_char_p(str(db_path)),
156
+ InteropUtil.string_to_char_p(alias._unescaped)))
157
+
158
+ def detach_database(self, alias: Union[str, Name, DatabaseName]):
159
+ """ Detaches a database from the underlying connection. """
160
+ alias = DatabaseName(alias)
161
+ Error.check(hapi.hyper_detach_database(self.__cdata, InteropUtil.string_to_char_p(alias._unescaped)))
162
+
163
+ def detach_all_databases(self):
164
+ """ Detaches all databases from the underlying connection. """
165
+ Error.check(hapi.hyper_detach_all_databases(self.__cdata))
166
+
167
+ def drop_database(self, database_path: Union[str, PurePath]):
168
+ """ Drops a database file. Raise an exception if the database does not exist. """
169
+ Error.check(hapi.hyper_drop_database(self.__cdata, InteropUtil.string_to_char_p(str(database_path)), True))
170
+
171
+ def drop_database_if_exists(self, database_path: Union[str, PurePath]):
172
+ """ Drops a database file if it exists, otherwise does nothing. """
173
+ Error.check(hapi.hyper_drop_database(self.__cdata, InteropUtil.string_to_char_p(str(database_path)), False))
@@ -0,0 +1,312 @@
1
+ import enum
2
+ import threading
3
+ import warnings
4
+
5
+ from pathlib import PurePath
6
+ from typing import Optional, Union, List, Mapping
7
+
8
+ from .endpoint import Endpoint
9
+ from .hyperexception import HyperException, ContextId
10
+ from .hyperserviceversion import HyperServiceVersion
11
+ from .result import Result
12
+ from .sqltype import NullableValue
13
+ from .warning import UnclosedObjectWarning
14
+ from .impl import hapi
15
+ from .impl.dll import ffi, lib
16
+ from .impl.dllutil import Error, Parameters, InteropUtil
17
+ from .impl.util import check_precondition
18
+ from . import catalog
19
+
20
+
21
+ class CreateMode(enum.Enum):
22
+ """ Constants which define what happens when connecting to a database depending on whether it already exists. """
23
+
24
+ NONE = hapi.HYPER_DO_NOT_CREATE
25
+ """ Do not create the database. Method will fail if database does not exist. """
26
+
27
+ CREATE = hapi.HYPER_CREATE
28
+ """ Create the database. Method will fail if the database already exists. """
29
+
30
+ CREATE_IF_NOT_EXISTS = hapi.HYPER_CREATE_IF_NOT_EXISTS
31
+ """ Create the database if it does not exist. """
32
+
33
+ CREATE_AND_REPLACE = hapi.HYPER_CREATE_AND_REPLACE
34
+ """ Create the database. If it already exists, drop the old one first. """
35
+
36
+
37
+ class Connection:
38
+ """
39
+ Connects to a Hyper server.
40
+
41
+ :param endpoint: :any:`Endpoint` which specifies the Hyper instance to connect to.
42
+ :param database: Optional path to the database file.
43
+ :param create_mode: If database path is specified, defines what happens if the database already exists. By default
44
+ it is :any:`CreateMode.NONE`.
45
+ :param parameters: Optional dictionary of connection parameters to pass to Hyper.
46
+ The available parameters are documented
47
+ `in the Tableau Hyper documentation, chapter "Connection Settings"
48
+ <https://tableau.github.io/hyper-db/docs/hyper-api/connection#connection-settings>`__.
49
+
50
+ If the database is not specified, then it connects to the main database. This is useful to create and delete
51
+ databases. Note that the main database gets deleted once the :any:`HyperProcess` gets closed.
52
+
53
+ No methods of this class are thread-safe, except :any:`cancel()`, which can be called from a different thread.
54
+
55
+ .. testsetup:: connection.__init__
56
+
57
+ import os
58
+ from tableauhyperapi import *
59
+ hyper = HyperProcess(Telemetry.SEND_USAGE_DATA_TO_TABLEAU, 'myapp')
60
+
61
+ .. testcode:: connection.__init__
62
+
63
+ # Connect and create the database. If it already exists, replace it.
64
+ with Connection(hyper.endpoint, 'mydb.hyper', CreateMode.CREATE_AND_REPLACE) as connection:
65
+ schema = TableDefinition('table', [
66
+ TableDefinition.Column('text', SqlType.text()),
67
+ TableDefinition.Column('int', SqlType.int()),
68
+ ])
69
+ connection.catalog.create_table(schema)
70
+
71
+ .. testcleanup:: connection.__init__
72
+
73
+ hyper.close()
74
+ if os.path.exists('mydb.hyper'):
75
+ os.remove('mydb.hyper')
76
+
77
+ """
78
+
79
+ def __init__(self, endpoint: Endpoint,
80
+ database: Optional[Union[str, PurePath]] = None,
81
+ create_mode: Optional[CreateMode] = CreateMode.NONE,
82
+ parameters: Optional[Mapping[str, str]] = None):
83
+ self.__cdata = None
84
+ # Reference lib for correct gc order
85
+ self.__lib_ref = lib
86
+
87
+ check_precondition(isinstance(endpoint, Endpoint), "'endpoint' must be an Endpoint instance")
88
+
89
+ if isinstance(database, PurePath):
90
+ database = str(database)
91
+
92
+ if parameters and 'dbname' in parameters:
93
+ if database:
94
+ raise ValueError("Database name cannot be provided as a 'database' parameter in addition to setting "
95
+ "'dbname' in the parameters dictionary")
96
+ database = parameters['dbname']
97
+ del parameters['dbname']
98
+
99
+ self.__cdata = self.__create_connection(endpoint, database, create_mode, parameters)
100
+ self.__endpoint = endpoint
101
+
102
+ # Lock to serialize cancel() and close() calls.
103
+ self.__cancel_lock = threading.Lock()
104
+
105
+ @staticmethod
106
+ def __create_connection(endpoint: Endpoint,
107
+ database: Optional[str],
108
+ create_mode: CreateMode,
109
+ parameters: Optional[Mapping[str, str]]):
110
+ native_params = Parameters.create_connection_parameters()
111
+ native_params.set_value('endpoint', endpoint.connection_descriptor)
112
+
113
+ if endpoint.user_agent:
114
+ native_params.set_value('user_agent', endpoint.user_agent)
115
+
116
+ native_params.set_value('api_language', 'Python')
117
+
118
+ if database:
119
+ native_params.set_value('dbname', database)
120
+
121
+ if parameters:
122
+ for key, value in parameters.items():
123
+ native_params.set_value(key, value)
124
+
125
+ pp = ffi.new('hyper_connection_t**')
126
+ Error.check(hapi.hyper_connect(native_params.cdata, pp, create_mode.value))
127
+ return ffi.gc(pp[0], hapi.hyper_disconnect)
128
+
129
+ @property
130
+ def _cdata(self):
131
+ if self.__cdata is None:
132
+ raise RuntimeError('Connection is closed')
133
+ return self.__cdata
134
+
135
+ @property
136
+ def _endpoint(self) -> Endpoint:
137
+ return self.__endpoint
138
+
139
+ @property
140
+ def is_open(self) -> bool:
141
+ """ Returns ``True`` if the connection has not been closed yet. """
142
+ return self.__cdata is not None
143
+
144
+ @property
145
+ def is_ready(self) -> bool:
146
+ """ Checks whether the connection is ready, i.e., it is not processing a query. An open :any:`Inserter` or
147
+ :any:`Result` keeps the connection busy. """
148
+ return self.__cdata is not None and hapi.hyper_connection_is_ready(self.__cdata)
149
+
150
+ def close(self):
151
+ """ Closes the connection. Note that this has no effect if there is an active result or data inserter.
152
+ These need to be closed before the connection to the server will be actually dropped."""
153
+
154
+ with self.__cancel_lock:
155
+ if self.__cdata is not None:
156
+ ffi.release(self.__cdata)
157
+ self.__cdata = None
158
+
159
+ def cancel(self):
160
+ """
161
+ Cancels the current SQL command or query of this connection (if any). This method may be safely called from
162
+ any thread. After this method was called, the current SQL command or query may fail with a cancellation error
163
+ at any point during its execution. However, there are no guarantees if and when it will fail.
164
+ """
165
+
166
+ with self.__cancel_lock:
167
+ if self.__cdata is not None:
168
+ try:
169
+ Error.check(hapi.hyper_cancel(self.__cdata))
170
+ except HyperException:
171
+ # TODO TFSID 921655: log it
172
+ pass
173
+
174
+ @property
175
+ def catalog(self) -> 'catalog.Catalog':
176
+ """ Gets the :any:`Catalog` for this connection. """
177
+ return catalog.Catalog(self)
178
+
179
+ def execute_query(self, query, text_as_bytes=False) -> Result:
180
+ """
181
+ Executes a SQL query and returns the result as a :any:`Result` object.
182
+
183
+ :param query: SQL query to execute.
184
+ :param text_as_bytes: optional, if ``True`` then string values read from the database will be returned as
185
+ UTF-8-encoded ``bytearray`` objects. By default string values are returned as ``str`` objects.
186
+ :return: A :any:`Result` instance. Use this method in a ``with`` statement to automatically close the result
187
+ when done reading from it, or call its :any:`close()<Result.close>` method. No queries can be executed or
188
+ tables created/opened while the result is open.
189
+ """
190
+ pp_result = ffi.new('hyper_rowset_t**')
191
+ Error.check(hapi.hyper_execute_query(self._cdata,
192
+ InteropUtil.string_to_char_p(query),
193
+ pp_result))
194
+ return Result(text_as_bytes, self, pp_result[0])
195
+
196
+ def execute_list_query(self, query, text_as_bytes=False) -> List[List[NullableValue]]:
197
+ """
198
+ Executes a SQL query and returns the result as list of rows of data, each represented by a list of objects.
199
+
200
+ :param query: SQL query to execute.
201
+ :param text_as_bytes: optional, if ``True`` then string values read from the database will be returned as
202
+ UTF-8-encoded ``bytearray`` objects. By default string values are returned as ``str`` objects.
203
+ :return: A list of rows, each represented by a list of objects. See :any:`TypeTag` documentation for how
204
+ database values are represented by Python objects.
205
+ """
206
+ with self.execute_query(query, text_as_bytes) as result:
207
+ # Note, it is tempting to return an iterable with yield, but that would make it easy to leak the
208
+ # result object until it's garbage-collected (if the iteration stops in the middle).
209
+ return list(result)
210
+
211
+ def execute_command(self, command) -> Optional[int]:
212
+ """
213
+ Executes a SQL statement and returns the affected row count if the statement has one.
214
+
215
+ :param command: SQL statement to execute.
216
+ :return: Count of affected rows if available, ``None`` otherwise.
217
+ """
218
+ row_count_cdata = ffi.new('int*')
219
+ Error.check(hapi.hyper_execute_command(self._cdata,
220
+ InteropUtil.string_to_char_p(command),
221
+ row_count_cdata))
222
+ row_count = row_count_cdata[0]
223
+ if row_count < 0:
224
+ row_count = None
225
+ return row_count
226
+
227
+ def execute_scalar_query(self, query, text_as_bytes=False) -> NullableValue:
228
+ """
229
+ Executes a scalar query, i.e. a query that returns exactly one row with one column, and returns the value
230
+ from the result.
231
+
232
+ :param query: SQL query to execute.
233
+ :param text_as_bytes: optional, if ``True`` then a string value read from the database will be returned as
234
+ UTF-8-encoded ``bytearray`` objects. By default string values are returned as ``str`` objects.
235
+ :return: the value from the result. A NULL database value is returned as ``None``. See :any:`TypeTag`
236
+ documentation for how database values are represented by Python objects.
237
+ """
238
+ with self.execute_query(query, text_as_bytes) as result:
239
+ if len(result.schema.columns) != 1:
240
+ raise HyperException(ContextId(0xA1B8BBEC6D), 'Query result must have exactly one column')
241
+ if not result.next_row():
242
+ raise HyperException(ContextId(0xB8BBEC6DA1), 'Query returned zero rows')
243
+ value = result.get_value(0)
244
+ if result.next_row():
245
+ raise HyperException(ContextId(0xBEC6DA1B8B), 'Query returned more than one row')
246
+ return value
247
+
248
+ def hyper_service_version(self) -> HyperServiceVersion:
249
+ """
250
+ Returns the Hyper Service version of this connection
251
+
252
+ :return: The Hyper Service version of this connection
253
+ """
254
+ version = ffi.new('hyper_service_version_t*')
255
+ Error.check(hapi.hyper_connection_get_hyper_service_version(self._cdata, version))
256
+ return HyperServiceVersion(version.major, version.minor)
257
+
258
+ def is_capability_active(self, capability_flag: str) -> bool:
259
+ """
260
+ Returns true if the capability flag is active on this connection.
261
+
262
+ :param capability_flag: The capability flag to check. It is prefixed with `capability_`.
263
+ :return: true if the capability flag is active on this connection.
264
+ """
265
+ return hapi.hyper_connection_is_capability_active(self._cdata, InteropUtil.string_to_char_p(capability_flag))
266
+
267
+ @staticmethod
268
+ def query_supported_hyper_service_version_range(endpoint: Endpoint) -> List[HyperServiceVersion]:
269
+ """ Connects to the Hyper endpoint and determines which Hyper Service version numbers are common
270
+ between the Hyper API and the Hyper server.
271
+
272
+ :param: endpoint Endpoint to connect to.
273
+ :return: List of Hyper Service versions that are supported by both this Hyper API and the endpoint.
274
+ """
275
+ native_params = Parameters.create_connection_parameters()
276
+ native_params.set_value('endpoint', endpoint.connection_descriptor)
277
+
278
+ if endpoint.user_agent:
279
+ native_params.set_value('user_agent', endpoint.user_agent)
280
+
281
+ native_params.set_value('api_language', 'Python')
282
+
283
+ version_array_ptr = ffi.new('hyper_service_version_t**')
284
+ elements_cdata = ffi.new('size_t*')
285
+ Error.check(hapi.hyper_query_supported_hyper_service_version_range(native_params.cdata,
286
+ version_array_ptr,
287
+ elements_cdata))
288
+ elements = elements_cdata[0]
289
+
290
+ if version_array_ptr[0] == ffi.NULL:
291
+ return []
292
+ else:
293
+ list = []
294
+ for i in range(0, elements):
295
+ list.append(HyperServiceVersion(version_array_ptr[0][i].major, version_array_ptr[0][i].minor))
296
+ return list
297
+
298
+ def __enter__(self):
299
+ return self
300
+
301
+ def __exit__(self, exc_type, exc_val, exc_tb):
302
+ self.close()
303
+
304
+ def __del__(self):
305
+ if self.__cdata is not None:
306
+ warnings.warn('Connection has not been closed. Use Connection object in a with statement or call its '
307
+ 'close() method when done.', UnclosedObjectWarning)
308
+ # it is closed by cffi, self.__cdata is a gc'ed pointer
309
+
310
+ def __repr__(self):
311
+ status = 'open' if self.is_ready else ('busy' if self.is_open else 'closed')
312
+ return f"<Connection object at {id(self):#x}; status: {status}; endpoint: {self._endpoint!r}>"