singlestoredb 1.16.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (183) hide show
  1. singlestoredb/__init__.py +75 -0
  2. singlestoredb/ai/__init__.py +2 -0
  3. singlestoredb/ai/chat.py +139 -0
  4. singlestoredb/ai/embeddings.py +128 -0
  5. singlestoredb/alchemy/__init__.py +90 -0
  6. singlestoredb/apps/__init__.py +3 -0
  7. singlestoredb/apps/_cloud_functions.py +90 -0
  8. singlestoredb/apps/_config.py +72 -0
  9. singlestoredb/apps/_connection_info.py +18 -0
  10. singlestoredb/apps/_dashboards.py +47 -0
  11. singlestoredb/apps/_process.py +32 -0
  12. singlestoredb/apps/_python_udfs.py +100 -0
  13. singlestoredb/apps/_stdout_supress.py +30 -0
  14. singlestoredb/apps/_uvicorn_util.py +36 -0
  15. singlestoredb/auth.py +245 -0
  16. singlestoredb/config.py +484 -0
  17. singlestoredb/connection.py +1487 -0
  18. singlestoredb/converters.py +950 -0
  19. singlestoredb/docstring/__init__.py +33 -0
  20. singlestoredb/docstring/attrdoc.py +126 -0
  21. singlestoredb/docstring/common.py +230 -0
  22. singlestoredb/docstring/epydoc.py +267 -0
  23. singlestoredb/docstring/google.py +412 -0
  24. singlestoredb/docstring/numpydoc.py +562 -0
  25. singlestoredb/docstring/parser.py +100 -0
  26. singlestoredb/docstring/py.typed +1 -0
  27. singlestoredb/docstring/rest.py +256 -0
  28. singlestoredb/docstring/tests/__init__.py +1 -0
  29. singlestoredb/docstring/tests/_pydoctor.py +21 -0
  30. singlestoredb/docstring/tests/test_epydoc.py +729 -0
  31. singlestoredb/docstring/tests/test_google.py +1007 -0
  32. singlestoredb/docstring/tests/test_numpydoc.py +1100 -0
  33. singlestoredb/docstring/tests/test_parse_from_object.py +109 -0
  34. singlestoredb/docstring/tests/test_parser.py +248 -0
  35. singlestoredb/docstring/tests/test_rest.py +547 -0
  36. singlestoredb/docstring/tests/test_util.py +70 -0
  37. singlestoredb/docstring/util.py +141 -0
  38. singlestoredb/exceptions.py +120 -0
  39. singlestoredb/functions/__init__.py +16 -0
  40. singlestoredb/functions/decorator.py +201 -0
  41. singlestoredb/functions/dtypes.py +1793 -0
  42. singlestoredb/functions/ext/__init__.py +1 -0
  43. singlestoredb/functions/ext/arrow.py +375 -0
  44. singlestoredb/functions/ext/asgi.py +2133 -0
  45. singlestoredb/functions/ext/json.py +420 -0
  46. singlestoredb/functions/ext/mmap.py +413 -0
  47. singlestoredb/functions/ext/rowdat_1.py +724 -0
  48. singlestoredb/functions/ext/timer.py +89 -0
  49. singlestoredb/functions/ext/utils.py +218 -0
  50. singlestoredb/functions/signature.py +1578 -0
  51. singlestoredb/functions/typing/__init__.py +41 -0
  52. singlestoredb/functions/typing/numpy.py +20 -0
  53. singlestoredb/functions/typing/pandas.py +2 -0
  54. singlestoredb/functions/typing/polars.py +2 -0
  55. singlestoredb/functions/typing/pyarrow.py +2 -0
  56. singlestoredb/functions/utils.py +421 -0
  57. singlestoredb/fusion/__init__.py +11 -0
  58. singlestoredb/fusion/graphql.py +213 -0
  59. singlestoredb/fusion/handler.py +916 -0
  60. singlestoredb/fusion/handlers/__init__.py +0 -0
  61. singlestoredb/fusion/handlers/export.py +525 -0
  62. singlestoredb/fusion/handlers/files.py +690 -0
  63. singlestoredb/fusion/handlers/job.py +660 -0
  64. singlestoredb/fusion/handlers/models.py +250 -0
  65. singlestoredb/fusion/handlers/stage.py +502 -0
  66. singlestoredb/fusion/handlers/utils.py +324 -0
  67. singlestoredb/fusion/handlers/workspace.py +956 -0
  68. singlestoredb/fusion/registry.py +249 -0
  69. singlestoredb/fusion/result.py +399 -0
  70. singlestoredb/http/__init__.py +27 -0
  71. singlestoredb/http/connection.py +1267 -0
  72. singlestoredb/magics/__init__.py +34 -0
  73. singlestoredb/magics/run_personal.py +137 -0
  74. singlestoredb/magics/run_shared.py +134 -0
  75. singlestoredb/management/__init__.py +9 -0
  76. singlestoredb/management/billing_usage.py +148 -0
  77. singlestoredb/management/cluster.py +462 -0
  78. singlestoredb/management/export.py +295 -0
  79. singlestoredb/management/files.py +1102 -0
  80. singlestoredb/management/inference_api.py +105 -0
  81. singlestoredb/management/job.py +887 -0
  82. singlestoredb/management/manager.py +373 -0
  83. singlestoredb/management/organization.py +226 -0
  84. singlestoredb/management/region.py +169 -0
  85. singlestoredb/management/utils.py +423 -0
  86. singlestoredb/management/workspace.py +1927 -0
  87. singlestoredb/mysql/__init__.py +177 -0
  88. singlestoredb/mysql/_auth.py +298 -0
  89. singlestoredb/mysql/charset.py +214 -0
  90. singlestoredb/mysql/connection.py +2032 -0
  91. singlestoredb/mysql/constants/CLIENT.py +38 -0
  92. singlestoredb/mysql/constants/COMMAND.py +32 -0
  93. singlestoredb/mysql/constants/CR.py +78 -0
  94. singlestoredb/mysql/constants/ER.py +474 -0
  95. singlestoredb/mysql/constants/EXTENDED_TYPE.py +3 -0
  96. singlestoredb/mysql/constants/FIELD_TYPE.py +48 -0
  97. singlestoredb/mysql/constants/FLAG.py +15 -0
  98. singlestoredb/mysql/constants/SERVER_STATUS.py +10 -0
  99. singlestoredb/mysql/constants/VECTOR_TYPE.py +6 -0
  100. singlestoredb/mysql/constants/__init__.py +0 -0
  101. singlestoredb/mysql/converters.py +271 -0
  102. singlestoredb/mysql/cursors.py +896 -0
  103. singlestoredb/mysql/err.py +92 -0
  104. singlestoredb/mysql/optionfile.py +20 -0
  105. singlestoredb/mysql/protocol.py +450 -0
  106. singlestoredb/mysql/tests/__init__.py +19 -0
  107. singlestoredb/mysql/tests/base.py +126 -0
  108. singlestoredb/mysql/tests/conftest.py +37 -0
  109. singlestoredb/mysql/tests/test_DictCursor.py +132 -0
  110. singlestoredb/mysql/tests/test_SSCursor.py +141 -0
  111. singlestoredb/mysql/tests/test_basic.py +452 -0
  112. singlestoredb/mysql/tests/test_connection.py +851 -0
  113. singlestoredb/mysql/tests/test_converters.py +58 -0
  114. singlestoredb/mysql/tests/test_cursor.py +141 -0
  115. singlestoredb/mysql/tests/test_err.py +16 -0
  116. singlestoredb/mysql/tests/test_issues.py +514 -0
  117. singlestoredb/mysql/tests/test_load_local.py +75 -0
  118. singlestoredb/mysql/tests/test_nextset.py +88 -0
  119. singlestoredb/mysql/tests/test_optionfile.py +27 -0
  120. singlestoredb/mysql/tests/thirdparty/__init__.py +6 -0
  121. singlestoredb/mysql/tests/thirdparty/test_MySQLdb/__init__.py +9 -0
  122. singlestoredb/mysql/tests/thirdparty/test_MySQLdb/capabilities.py +323 -0
  123. singlestoredb/mysql/tests/thirdparty/test_MySQLdb/dbapi20.py +865 -0
  124. singlestoredb/mysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_capabilities.py +110 -0
  125. singlestoredb/mysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_dbapi20.py +224 -0
  126. singlestoredb/mysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_nonstandard.py +101 -0
  127. singlestoredb/mysql/times.py +23 -0
  128. singlestoredb/notebook/__init__.py +16 -0
  129. singlestoredb/notebook/_objects.py +213 -0
  130. singlestoredb/notebook/_portal.py +352 -0
  131. singlestoredb/py.typed +0 -0
  132. singlestoredb/pytest.py +352 -0
  133. singlestoredb/server/__init__.py +0 -0
  134. singlestoredb/server/docker.py +452 -0
  135. singlestoredb/server/free_tier.py +267 -0
  136. singlestoredb/tests/__init__.py +0 -0
  137. singlestoredb/tests/alltypes.sql +307 -0
  138. singlestoredb/tests/alltypes_no_nulls.sql +208 -0
  139. singlestoredb/tests/empty.sql +0 -0
  140. singlestoredb/tests/ext_funcs/__init__.py +702 -0
  141. singlestoredb/tests/local_infile.csv +3 -0
  142. singlestoredb/tests/test.ipynb +18 -0
  143. singlestoredb/tests/test.sql +680 -0
  144. singlestoredb/tests/test2.ipynb +18 -0
  145. singlestoredb/tests/test2.sql +1 -0
  146. singlestoredb/tests/test_basics.py +1332 -0
  147. singlestoredb/tests/test_config.py +318 -0
  148. singlestoredb/tests/test_connection.py +3103 -0
  149. singlestoredb/tests/test_dbapi.py +27 -0
  150. singlestoredb/tests/test_exceptions.py +45 -0
  151. singlestoredb/tests/test_ext_func.py +1472 -0
  152. singlestoredb/tests/test_ext_func_data.py +1101 -0
  153. singlestoredb/tests/test_fusion.py +1527 -0
  154. singlestoredb/tests/test_http.py +288 -0
  155. singlestoredb/tests/test_management.py +1599 -0
  156. singlestoredb/tests/test_plugin.py +33 -0
  157. singlestoredb/tests/test_results.py +171 -0
  158. singlestoredb/tests/test_types.py +132 -0
  159. singlestoredb/tests/test_udf.py +737 -0
  160. singlestoredb/tests/test_udf_returns.py +459 -0
  161. singlestoredb/tests/test_vectorstore.py +51 -0
  162. singlestoredb/tests/test_xdict.py +333 -0
  163. singlestoredb/tests/utils.py +141 -0
  164. singlestoredb/types.py +373 -0
  165. singlestoredb/utils/__init__.py +0 -0
  166. singlestoredb/utils/config.py +950 -0
  167. singlestoredb/utils/convert_rows.py +69 -0
  168. singlestoredb/utils/debug.py +13 -0
  169. singlestoredb/utils/dtypes.py +205 -0
  170. singlestoredb/utils/events.py +65 -0
  171. singlestoredb/utils/mogrify.py +151 -0
  172. singlestoredb/utils/results.py +585 -0
  173. singlestoredb/utils/xdict.py +425 -0
  174. singlestoredb/vectorstore.py +192 -0
  175. singlestoredb/warnings.py +5 -0
  176. singlestoredb-1.16.1.dist-info/METADATA +165 -0
  177. singlestoredb-1.16.1.dist-info/RECORD +183 -0
  178. singlestoredb-1.16.1.dist-info/WHEEL +5 -0
  179. singlestoredb-1.16.1.dist-info/entry_points.txt +2 -0
  180. singlestoredb-1.16.1.dist-info/licenses/LICENSE +201 -0
  181. singlestoredb-1.16.1.dist-info/top_level.txt +3 -0
  182. sqlx/__init__.py +4 -0
  183. sqlx/magic.py +113 -0
@@ -0,0 +1,413 @@
1
+ #!/usr/bin/env python
2
+ """
3
+ Module for creating collocated Python UDFs
4
+
5
+ This module implements the collocated form of external functions for
6
+ SingleStoreDB. This mode uses a socket for control communications
7
+ and memory mapped files for passing the data to and from the UDF.
8
+
9
+ The command below is a sample invocation. It exports all functions
10
+ within the `myfuncs` Python module that have a `@udf` decorator on
11
+ them. The `--db` option specifies a database connection string.
12
+ If this exists, the UDF application will connect to the database
13
+ and register all functions. The `--replace-existing` option indicates
14
+ that existing functions should be replaced::
15
+
16
+ python -m singlestoredb.functions.ext.mmap \
17
+ --db=root:@127.0.0.1:9306/cosmeticshop --replace-existing \
18
+ myfuncs
19
+
20
+ The `myfuncs` package can be any Python package in your Python path.
21
+ It must contain functions marked with a `@udf` decorator and the
22
+ types must be annotated or specified using the `@udf` decorator
23
+ similar to the following::
24
+
25
+ from singlestoredb.functions import udf
26
+
27
+ @udf
28
+ def print_it(x2: float, x3: str) -> str:
29
+ return int(x2) * x3
30
+
31
+ @udf.pandas
32
+ def print_it_pandas(x2: float, x3: str) -> str:
33
+ return x2.astype(np.int64) * x3.astype(str)
34
+
35
+ With the functions registered, you can now run the UDFs::
36
+
37
+ SELECT print_it(3.14, 'my string');
38
+ SELECT print_it_pandas(3.14, 'my string');
39
+
40
+ """
41
+ import argparse
42
+ import array
43
+ import asyncio
44
+ import io
45
+ import logging
46
+ import mmap
47
+ import multiprocessing
48
+ import os
49
+ import secrets
50
+ import socket
51
+ import struct
52
+ import sys
53
+ import tempfile
54
+ import threading
55
+ import traceback
56
+ import urllib
57
+ import zipfile
58
+ from typing import Any
59
+ from typing import Dict
60
+ from typing import List
61
+ from typing import Optional
62
+
63
+ from . import asgi
64
+ from . import utils
65
+ from ... import manage_workspaces
66
+ from ...config import get_option
67
+
68
+
69
+ logger = utils.get_logger('singlestoredb.functions.ext.mmap')
70
+
71
+
72
+ def _handle_request(app: Any, connection: Any, client_address: Any) -> None:
73
+ """
74
+ Handle function call request.
75
+
76
+ Parameters:
77
+ app : ASGI app
78
+ An ASGI application from the singlestoredb.functions.ext.asgi module
79
+ connection : socket connection
80
+ Socket connection for function control messages
81
+ client_address : string
82
+ Address of connecting client
83
+
84
+ """
85
+ logger.info('connection from {}'.format(str(connection).split(', ')[0][-4:]))
86
+
87
+ # Receive the request header. Format:
88
+ # server version: uint64
89
+ # length of function name: uint64
90
+ buf = connection.recv(16)
91
+ version, namelen = struct.unpack('<qq', buf)
92
+
93
+ # Python's recvmsg returns a tuple. We only really care about the first
94
+ # two parts. The recvmsg call has a weird way of specifying the size for
95
+ # the file descriptor array; basically, we're indicating we want to read
96
+ # two 32-bit ints (for the input and output files).
97
+ fd_model = array.array('i', [0, 0])
98
+ msg, ancdata, flags, addr = connection.recvmsg(
99
+ namelen,
100
+ socket.CMSG_LEN(2 * fd_model.itemsize),
101
+ )
102
+ assert len(ancdata) == 1
103
+
104
+ # The function's name will be in the "message" area of the recvmsg response.
105
+ # It will be populated with `namelen` bytes.
106
+ name = msg.decode('utf8')
107
+
108
+ # Two file descriptors are transferred to us from the database via the
109
+ # `sendmsg` protocol. These are for reading the input rows and writing
110
+ # the output rows, respectively.
111
+ fd0, fd1 = struct.unpack('<ii', ancdata[0][2])
112
+ ifile = os.fdopen(fd0, 'rb')
113
+ ofile = os.fdopen(fd1, 'wb')
114
+
115
+ # Keep receiving data on this socket until we run out.
116
+ while True:
117
+
118
+ # Read in the length of this row, a uint64. No data means we're done
119
+ # receiving.
120
+ buf = connection.recv(8)
121
+ if not buf:
122
+ break
123
+ length = struct.unpack('<q', buf)[0]
124
+ if not length:
125
+ break
126
+
127
+ # Map in the input shared memory segment from the fd we received via
128
+ # recvmsg.
129
+ mem = mmap.mmap(
130
+ ifile.fileno(),
131
+ length,
132
+ mmap.MAP_SHARED,
133
+ mmap.PROT_READ,
134
+ )
135
+
136
+ # Read row data
137
+ response_size = 0
138
+ out = io.BytesIO()
139
+
140
+ ifile.seek(0)
141
+ try:
142
+ # Run the function
143
+ asyncio.run(
144
+ app.call(
145
+ name,
146
+ io.BytesIO(ifile.read(length)),
147
+ out,
148
+ data_format='rowdat_1',
149
+ data_version='1.0',
150
+ ),
151
+ )
152
+
153
+ # Write results
154
+ buf = out.getbuffer()
155
+ response_size = len(buf)
156
+ ofile.truncate(max(128*1024, response_size))
157
+ ofile.seek(0)
158
+ ofile.write(buf)
159
+ ofile.flush()
160
+
161
+ # Complete the request by send back the status as two uint64s on the
162
+ # socket:
163
+ # - http status
164
+ # - size of data in output shared memory
165
+ connection.send(struct.pack('<qq', 200, response_size))
166
+
167
+ except Exception as exc:
168
+ errmsg = f'error occurred in executing function `{name}`: {exc}\n'
169
+ logger.error(errmsg.rstrip())
170
+ for line in traceback.format_exception(exc): # type: ignore
171
+ logger.error(line.rstrip())
172
+ connection.send(
173
+ struct.pack(
174
+ f'<qq{len(errmsg)}s', 500,
175
+ len(errmsg), errmsg.encode('utf8'),
176
+ ),
177
+ )
178
+ break
179
+
180
+ finally:
181
+ # Close the shared memory object.
182
+ mem.close()
183
+
184
+ # Close shared memory files.
185
+ ifile.close()
186
+ ofile.close()
187
+
188
+ # Close the connection
189
+ connection.close()
190
+
191
+
192
+ def main(argv: Optional[List[str]] = None) -> None:
193
+ """
194
+ Main program for collocated Python UDFs
195
+
196
+ Parameters
197
+ ----------
198
+ argv : List[str], optional
199
+ List of command-line parameters
200
+
201
+ """
202
+ tmpdir = None
203
+ functions = []
204
+ defaults: Dict[str, Any] = {}
205
+ for i in range(2):
206
+ parser = argparse.ArgumentParser(
207
+ prog='python -m singlestoredb.functions.ext.mmap',
208
+ description='Run a collacated Python UDF server',
209
+ )
210
+ parser.add_argument(
211
+ '--max-connections', metavar='n', type=int,
212
+ default=get_option('external_function.max_connections'),
213
+ help='maximum number of server connections before refusing them',
214
+ )
215
+ parser.add_argument(
216
+ '--single-thread', action='store_true',
217
+ default=get_option('external_function.single_thread'),
218
+ help='should the server run in single-thread mode?',
219
+ )
220
+ parser.add_argument(
221
+ '--socket-path', metavar='file-path',
222
+ default=(
223
+ get_option('external_function.socket_path') or
224
+ os.path.join(tempfile.gettempdir(), secrets.token_hex(16))
225
+ ),
226
+ help='path to communications socket',
227
+ )
228
+ parser.add_argument(
229
+ '--db', metavar='conn-str',
230
+ default=os.environ.get('SINGLESTOREDB_URL', ''),
231
+ help='connection string to use for registering functions',
232
+ )
233
+ parser.add_argument(
234
+ '--replace-existing', action='store_true',
235
+ help='should existing functions of the same name '
236
+ 'in the database be replaced?',
237
+ )
238
+ parser.add_argument(
239
+ '--log-level', metavar='[info|debug|warning|error]',
240
+ default=get_option('external_function.log_level'),
241
+ help='logging level',
242
+ )
243
+ parser.add_argument(
244
+ '--process-mode', metavar='[thread|subprocess]',
245
+ default=get_option('external_function.process_mode'),
246
+ help='how to handle concurrent handlers',
247
+ )
248
+ parser.add_argument(
249
+ 'functions', metavar='module.or.func.path', nargs='*',
250
+ help='functions or modules to export in UDF server',
251
+ )
252
+
253
+ args = parser.parse_args(argv)
254
+
255
+ logger.setLevel(getattr(logging, args.log_level.upper()))
256
+
257
+ if i > 0:
258
+ break
259
+
260
+ # Download Stage files as needed
261
+ for i, f in enumerate(args.functions):
262
+ if f.startswith('stage://'):
263
+ url = urllib.parse.urlparse(f)
264
+ if not url.path or url.path == '/':
265
+ raise ValueError(f'no stage path was specified: {f}')
266
+ if url.path.endswith('/'):
267
+ raise ValueError(f'an environment file must be specified: {f}')
268
+
269
+ mgr = manage_workspaces()
270
+ if url.hostname:
271
+ wsg = mgr.get_workspace_group(url.hostname)
272
+ elif os.environ.get('SINGLESTOREDB_WORKSPACE_GROUP'):
273
+ wsg = mgr.get_workspace_group(
274
+ os.environ['SINGLESTOREDB_WORKSPACE_GROUP'],
275
+ )
276
+ else:
277
+ raise ValueError(f'no workspace group specified: {f}')
278
+
279
+ if tmpdir is None:
280
+ tmpdir = tempfile.TemporaryDirectory()
281
+
282
+ local_path = os.path.join(tmpdir.name, url.path.split('/')[-1])
283
+ wsg.stage.download_file(url.path, local_path)
284
+ args.functions[i] = local_path
285
+
286
+ elif f.startswith('http://') or f.startswith('https://'):
287
+ if tmpdir is None:
288
+ tmpdir = tempfile.TemporaryDirectory()
289
+
290
+ local_path = os.path.join(tmpdir.name, f.split('/')[-1])
291
+ urllib.request.urlretrieve(f, local_path)
292
+ args.functions[i] = local_path
293
+
294
+ # See if any of the args are zip files (assume they are environment files)
295
+ modules = [(x, zipfile.is_zipfile(x)) for x in args.functions]
296
+ envs = [x[0] for x in modules if x[1]]
297
+ others = [x[0] for x in modules if not x[1]]
298
+
299
+ if envs and len(envs) > 1:
300
+ raise RuntimeError('only one environment file may be specified.')
301
+
302
+ if envs and others:
303
+ raise RuntimeError('environment files and other modules can not be mixed.')
304
+
305
+ # See if an environment file was specified. If so, use those settings
306
+ # as the defaults and reprocess command line.
307
+ if envs:
308
+ # Add zip file to the Python path
309
+ sys.path.insert(0, envs[0])
310
+ functions = [os.path.splitext(os.path.basename(envs[0]))[0]]
311
+
312
+ # Add pyproject.toml variables and redo command-line processing
313
+ defaults = utils.read_config(
314
+ envs[0],
315
+ ['tool.external_function', 'tool.external-function.collocated'],
316
+ )
317
+ if defaults:
318
+ continue
319
+
320
+ args.functions = functions or args.functions or None
321
+ args.replace_existing = args.replace_existing \
322
+ or defaults.get('replace_existing') \
323
+ or get_option('external_function.replace_existing')
324
+
325
+ if os.path.exists(args.socket_path):
326
+ try:
327
+ os.unlink(args.socket_path)
328
+ except (IOError, OSError):
329
+ raise RuntimeError(
330
+ f'could not remove existing socket path: {args.socket_path}',
331
+ )
332
+
333
+ # Create application from functions / module
334
+ app = asgi.create_app(
335
+ functions=args.functions,
336
+ url=args.socket_path,
337
+ data_format='rowdat_1',
338
+ app_mode='collocated',
339
+ )
340
+
341
+ funcs = app.get_create_functions(replace=args.replace_existing)
342
+ if not funcs:
343
+ raise RuntimeError('no functions specified')
344
+
345
+ for f in funcs:
346
+ logger.info(f'function: {f}')
347
+
348
+ # Create the Unix socket server.
349
+ server = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
350
+
351
+ # Bind our server to the path.
352
+ server.bind(args.socket_path)
353
+
354
+ logger.info(f'using socket path: {args.socket_path}')
355
+
356
+ # Listen for incoming connections. Argument is the number of connections to
357
+ # keep in the backlog before we begin refusing them; 32 is plenty for this
358
+ # simple case.
359
+ server.listen(args.max_connections)
360
+
361
+ try:
362
+ # Register functions with database
363
+ if args.db:
364
+ logger.info('registering functions with database')
365
+ app.register_functions(args.db, replace=args.replace_existing)
366
+
367
+ # Accept connections forever.
368
+ while True:
369
+ # Listen for the next connection on our port.
370
+ connection, client_address = server.accept()
371
+
372
+ if args.process_mode == 'thread':
373
+ tcls = threading.Thread
374
+ else:
375
+ tcls = multiprocessing.Process # type: ignore
376
+
377
+ t = tcls(
378
+ target=_handle_request,
379
+ args=(app, connection, client_address),
380
+ )
381
+
382
+ t.start()
383
+
384
+ # NOTE: The following line forces this process to handle requests
385
+ # serially. This makes it easier to understand what's going on.
386
+ # In real life, though, parallel is much faster. To use parallel
387
+ # handling, just comment out the next line.
388
+ if args.single_thread:
389
+ t.join()
390
+
391
+ except KeyboardInterrupt:
392
+ return
393
+
394
+ finally:
395
+ if args.db:
396
+ logger.info('dropping functions from database')
397
+ app.drop_functions(args.db)
398
+
399
+ # Remove the socket file before we exit.
400
+ try:
401
+ os.unlink(args.socket_path)
402
+ except (IOError, OSError):
403
+ logger.error(f'could not remove socket path: {args.socket_path}')
404
+
405
+
406
+ if __name__ == '__main__':
407
+ try:
408
+ main()
409
+ except RuntimeError as exc:
410
+ logger.error(str(exc))
411
+ sys.exit(1)
412
+ except KeyboardInterrupt:
413
+ pass