singlestoredb 1.12.4__py3-none-any.whl → 1.13.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.

Potentially problematic release.


This version of singlestoredb might be problematic. Click here for more details.

Files changed (35) hide show
  1. singlestoredb/__init__.py +1 -1
  2. singlestoredb/ai/__init__.py +1 -0
  3. singlestoredb/ai/chat.py +26 -0
  4. singlestoredb/ai/embeddings.py +18 -15
  5. singlestoredb/apps/__init__.py +1 -0
  6. singlestoredb/apps/_config.py +6 -0
  7. singlestoredb/apps/_connection_info.py +8 -0
  8. singlestoredb/apps/_python_udfs.py +85 -0
  9. singlestoredb/config.py +14 -2
  10. singlestoredb/functions/__init__.py +15 -1
  11. singlestoredb/functions/decorator.py +102 -252
  12. singlestoredb/functions/dtypes.py +545 -198
  13. singlestoredb/functions/ext/asgi.py +421 -129
  14. singlestoredb/functions/ext/json.py +29 -36
  15. singlestoredb/functions/ext/mmap.py +1 -1
  16. singlestoredb/functions/ext/rowdat_1.py +50 -70
  17. singlestoredb/functions/signature.py +816 -144
  18. singlestoredb/functions/typing.py +41 -0
  19. singlestoredb/functions/utils.py +421 -0
  20. singlestoredb/http/connection.py +3 -1
  21. singlestoredb/management/inference_api.py +101 -0
  22. singlestoredb/management/manager.py +6 -1
  23. singlestoredb/management/organization.py +17 -0
  24. singlestoredb/management/utils.py +2 -2
  25. singlestoredb/tests/ext_funcs/__init__.py +476 -237
  26. singlestoredb/tests/test_ext_func.py +192 -3
  27. singlestoredb/tests/test_management.py +5 -5
  28. singlestoredb/tests/test_udf.py +101 -131
  29. singlestoredb/tests/test_udf_returns.py +459 -0
  30. {singlestoredb-1.12.4.dist-info → singlestoredb-1.13.1.dist-info}/METADATA +2 -1
  31. {singlestoredb-1.12.4.dist-info → singlestoredb-1.13.1.dist-info}/RECORD +35 -29
  32. {singlestoredb-1.12.4.dist-info → singlestoredb-1.13.1.dist-info}/LICENSE +0 -0
  33. {singlestoredb-1.12.4.dist-info → singlestoredb-1.13.1.dist-info}/WHEEL +0 -0
  34. {singlestoredb-1.12.4.dist-info → singlestoredb-1.13.1.dist-info}/entry_points.txt +0 -0
  35. {singlestoredb-1.12.4.dist-info → singlestoredb-1.13.1.dist-info}/top_level.txt +0 -0
singlestoredb/__init__.py CHANGED
@@ -13,7 +13,7 @@ Examples
13
13
 
14
14
  """
15
15
 
16
- __version__ = '1.12.4'
16
+ __version__ = '1.13.1'
17
17
 
18
18
  from typing import Any
19
19
 
@@ -1 +1,2 @@
1
+ from .chat import SingleStoreChatOpenAI # noqa: F401
1
2
  from .embeddings import SingleStoreEmbeddings # noqa: F401
@@ -0,0 +1,26 @@
1
+ import os
2
+ from typing import Any
3
+
4
+ from singlestoredb.fusion.handlers.utils import get_workspace_manager
5
+
6
+ try:
7
+ from langchain_openai import ChatOpenAI
8
+ except ImportError:
9
+ raise ImportError(
10
+ 'Could not import langchain_openai python package. '
11
+ 'Please install it with `pip install langchain_openai`.',
12
+ )
13
+
14
+
15
+ class SingleStoreChatOpenAI(ChatOpenAI):
16
+ def __init__(self, model_name: str, **kwargs: Any):
17
+ inference_api_manger = (
18
+ get_workspace_manager().organizations.current.inference_apis
19
+ )
20
+ info = inference_api_manger.get(model_name=model_name)
21
+ super().__init__(
22
+ base_url=info.connection_url,
23
+ api_key=os.environ.get('SINGLESTOREDB_USER_TOKEN'),
24
+ model=model_name,
25
+ **kwargs,
26
+ )
@@ -1,24 +1,27 @@
1
- import os as _os
1
+ import os
2
2
  from typing import Any
3
3
 
4
+ from singlestoredb.fusion.handlers.utils import get_workspace_manager
5
+
4
6
  try:
5
- from langchain_community.embeddings.ollama import OllamaEmbeddings
7
+ from langchain_openai import OpenAIEmbeddings
6
8
  except ImportError:
7
9
  raise ImportError(
8
- 'Could not import langchain_community python package. '
9
- 'Please install it with `pip install langchain_community`.',
10
+ 'Could not import langchain_openai python package. '
11
+ 'Please install it with `pip install langchain_openai`.',
10
12
  )
11
13
 
12
14
 
13
- class SingleStoreEmbeddings(OllamaEmbeddings):
14
-
15
- def __init__(self, **kwargs: Any):
16
- url = _os.getenv('SINGLESTORE_AI_EXPERIMENTAL_URL')
17
- if not url:
18
- raise ValueError(
19
- "Environment variable 'SINGLESTORE_AI_EXPERIMENTAL_URL' must be set",
20
- )
15
+ class SingleStoreEmbeddings(OpenAIEmbeddings):
21
16
 
22
- base_url = url.strip('/v1')
23
- kwargs = {'model': 'nomic-embed-text', **kwargs}
24
- super().__init__(base_url=base_url, **kwargs)
17
+ def __init__(self, model_name: str, **kwargs: Any):
18
+ inference_api_manger = (
19
+ get_workspace_manager().organizations.current.inference_apis
20
+ )
21
+ info = inference_api_manger.get(model_name=model_name)
22
+ super().__init__(
23
+ base_url=info.connection_url,
24
+ api_key=os.environ.get('SINGLESTOREDB_USER_TOKEN'),
25
+ model=model_name,
26
+ **kwargs,
27
+ )
@@ -1,2 +1,3 @@
1
1
  from ._cloud_functions import run_function_app # noqa: F401
2
2
  from ._dashboards import run_dashboard_app # noqa: F401
3
+ from ._python_udfs import run_udf_app # noqa: F401
@@ -8,10 +8,12 @@ class AppConfig:
8
8
  listen_port: int
9
9
  base_url: str
10
10
  base_path: str
11
+ notebook_server_id: str
11
12
  app_token: Optional[str]
12
13
  user_token: Optional[str]
13
14
  running_interactively: bool
14
15
  is_gateway_enabled: bool
16
+ is_local_dev: bool
15
17
 
16
18
  @staticmethod
17
19
  def _read_variable(name: str) -> str:
@@ -28,6 +30,8 @@ class AppConfig:
28
30
  port = cls._read_variable('SINGLESTOREDB_APP_LISTEN_PORT')
29
31
  base_url = cls._read_variable('SINGLESTOREDB_APP_BASE_URL')
30
32
  base_path = cls._read_variable('SINGLESTOREDB_APP_BASE_PATH')
33
+ notebook_server_id = cls._read_variable('SINGLESTOREDB_NOTEBOOK_SERVER_ID')
34
+ is_local_dev_env_var = cls._read_variable('SINGLESTOREDB_IS_LOCAL_DEV')
31
35
 
32
36
  workload_type = os.environ.get('SINGLESTOREDB_WORKLOAD_TYPE')
33
37
  running_interactively = workload_type == 'InteractiveNotebook'
@@ -49,10 +53,12 @@ class AppConfig:
49
53
  listen_port=int(port),
50
54
  base_url=base_url,
51
55
  base_path=base_path,
56
+ notebook_server_id=notebook_server_id,
52
57
  app_token=app_token,
53
58
  user_token=user_token,
54
59
  running_interactively=running_interactively,
55
60
  is_gateway_enabled=is_gateway_enabled,
61
+ is_local_dev=is_local_dev_env_var == 'true',
56
62
  )
57
63
 
58
64
  @property
@@ -1,4 +1,6 @@
1
1
  from dataclasses import dataclass
2
+ from typing import Any
3
+ from typing import Dict
2
4
  from typing import Optional
3
5
 
4
6
 
@@ -8,3 +10,9 @@ class ConnectionInfo:
8
10
 
9
11
  # Only present in interactive mode
10
12
  token: Optional[str]
13
+
14
+
15
+ @dataclass
16
+ class UdfConnectionInfo:
17
+ url: str
18
+ functions: Dict[str, Any]
@@ -0,0 +1,85 @@
1
+ import asyncio
2
+ import os
3
+ import typing
4
+
5
+ from ..functions.ext.asgi import Application
6
+ from ._config import AppConfig
7
+ from ._connection_info import UdfConnectionInfo
8
+ from ._process import kill_process_by_port
9
+
10
+ if typing.TYPE_CHECKING:
11
+ from ._uvicorn_util import AwaitableUvicornServer
12
+
13
+ # Keep track of currently running server
14
+ _running_server: 'typing.Optional[AwaitableUvicornServer]' = None
15
+
16
+
17
+ async def run_udf_app(
18
+ replace_existing: bool,
19
+ log_level: str = 'error',
20
+ kill_existing_app_server: bool = True,
21
+ ) -> UdfConnectionInfo:
22
+ global _running_server
23
+ from ._uvicorn_util import AwaitableUvicornServer
24
+
25
+ try:
26
+ import uvicorn
27
+ except ImportError:
28
+ raise ImportError('package uvicorn is required to run python udfs')
29
+
30
+ app_config = AppConfig.from_env()
31
+
32
+ if kill_existing_app_server:
33
+ # Shutdown the server gracefully if it was started by us.
34
+ # Since the uvicorn server doesn't start a new subprocess
35
+ # killing the process would result in kernel dying.
36
+ if _running_server is not None:
37
+ await _running_server.shutdown()
38
+ _running_server = None
39
+
40
+ # Kill if any other process is occupying the port
41
+ kill_process_by_port(app_config.listen_port)
42
+
43
+ base_url = generate_base_url(app_config)
44
+
45
+ udf_suffix = ''
46
+ if app_config.running_interactively:
47
+ udf_suffix = '_test'
48
+ app = Application(url=base_url, app_mode='managed', name_suffix=udf_suffix)
49
+
50
+ config = uvicorn.Config(
51
+ app,
52
+ host='0.0.0.0',
53
+ port=app_config.listen_port,
54
+ log_level=log_level,
55
+ )
56
+ _running_server = AwaitableUvicornServer(config)
57
+
58
+ # Register the functions
59
+ app.register_functions(replace=replace_existing)
60
+
61
+ asyncio.create_task(_running_server.serve())
62
+ await _running_server.wait_for_startup()
63
+
64
+ print(f'Python UDF registered at {base_url}')
65
+
66
+ return UdfConnectionInfo(base_url, app.get_function_info())
67
+
68
+
69
+ def generate_base_url(app_config: AppConfig) -> str:
70
+ if not app_config.is_gateway_enabled:
71
+ raise RuntimeError('Python UDFs are not available if Nova Gateway is not enabled')
72
+
73
+ if not app_config.running_interactively:
74
+ return app_config.base_url
75
+
76
+ # generate python udf endpoint for interactive notebooks
77
+ gateway_url = os.environ.get('SINGLESTOREDB_NOVA_GATEWAY_ENDPOINT')
78
+ if app_config.is_local_dev:
79
+ gateway_url = os.environ.get('SINGLESTOREDB_NOVA_GATEWAY_DEV_ENDPOINT')
80
+ if gateway_url is None:
81
+ raise RuntimeError(
82
+ 'Missing SINGLESTOREDB_NOVA_GATEWAY_DEV_ENDPOINT environment variable.',
83
+ )
84
+
85
+ return f'{gateway_url}/pythonudfs/{app_config.notebook_server_id}/interactive/'
singlestoredb/config.py CHANGED
@@ -317,7 +317,7 @@ register_option(
317
317
  'external_function.app_mode', 'string',
318
318
  functools.partial(
319
319
  check_str,
320
- valid_values=['remote', 'collocated'],
320
+ valid_values=['remote', 'collocated', 'managed'],
321
321
  ),
322
322
  'remote',
323
323
  'Specifies the mode of operation of the external function application.',
@@ -407,6 +407,18 @@ register_option(
407
407
  environ=['SINGLESTOREDB_EXT_FUNC_LOG_LEVEL'],
408
408
  )
409
409
 
410
+ register_option(
411
+ 'external_function.name_prefix', 'string', check_str, '',
412
+ 'Prefix to add to external function names.',
413
+ environ=['SINGLESTOREDB_EXT_FUNC_NAME_PREFIX'],
414
+ )
415
+
416
+ register_option(
417
+ 'external_function.name_suffix', 'string', check_str, '',
418
+ 'Suffix to add to external function names.',
419
+ environ=['SINGLESTOREDB_EXT_FUNC_NAME_SUFFIX'],
420
+ )
421
+
410
422
  register_option(
411
423
  'external_function.connection', 'string', check_str,
412
424
  os.environ.get('SINGLESTOREDB_URL') or None,
@@ -415,7 +427,7 @@ register_option(
415
427
  )
416
428
 
417
429
  register_option(
418
- 'external_function.host', 'string', check_str, '127.0.0.1',
430
+ 'external_function.host', 'string', check_str, 'localhost',
419
431
  'Specifies the host to bind the server to.',
420
432
  environ=['SINGLESTOREDB_EXT_FUNC_HOST'],
421
433
  )
@@ -1,2 +1,16 @@
1
- from .decorator import tvf # noqa: F401
2
1
  from .decorator import udf # noqa: F401
2
+ from .typing import Masked # noqa: F401
3
+ from .typing import Table # noqa: F401
4
+ from .utils import pack_vector # noqa: F401
5
+ from .utils import pack_vectors # noqa: F401
6
+ from .utils import unpack_vector # noqa: F401
7
+ from .utils import unpack_vectors # noqa: F401
8
+ from .utils import VectorTypes
9
+
10
+
11
+ F32 = VectorTypes.F32
12
+ F64 = VectorTypes.F64
13
+ I8 = VectorTypes.I8
14
+ I16 = VectorTypes.I16
15
+ I32 = VectorTypes.I32
16
+ I64 = VectorTypes.I64