meerschaum.connectors.api

Interact with the Meerschaum API (send commands, pull data, etc.)

 1#! /usr/bin/env python
 2# -*- coding: utf-8 -*-
 3# vim:fenc=utf-8
 4
 5"""
 6Interact with the Meerschaum API (send commands, pull data, etc.)
 7"""
 8
 9from meerschaum.connectors.api._APIConnector import APIConnector
10
11__all__ = ('APIConnector',)
class APIConnector(meerschaum.connectors.instance._InstanceConnector.InstanceConnector):
 23class APIConnector(InstanceConnector):
 24    """
 25    Connect to a Meerschaum API instance.
 26
 27    The requests are made by the Rust client (`meerschaum._core.ApiConnector`, one per connector
 28    and process): the same routes, query parameters and bodies as v4. Nothing here imports
 29    `requests` or `httpx`.
 30    """
 31
 32    IS_THREAD_SAFE: bool = False
 33    OPTIONAL_ATTRIBUTES: List[str] = ['port', 'client_secret', 'client_id', 'api_key']
 34
 35    from ._request import (
 36        make_request,
 37        get,
 38        post,
 39        put,
 40        patch,
 41        delete,
 42        wget,
 43    )
 44    from ._actions import (
 45        get_actions,
 46        do_action,
 47        do_action_async,
 48        do_action_legacy,
 49    )
 50    from ._misc import get_mrsm_version, get_chaining_status
 51    from ._metrics import get_engine_metrics, initialize_engine_metrics
 52    from ._pipes import (
 53        get_pipe_instance_keys,
 54        register_pipe,
 55        fetch_pipes_keys,
 56        edit_pipe,
 57        sync_pipe,
 58        delete_pipe,
 59        delete_pipe_cache,
 60        get_pipe_data,
 61        get_pipe_id,
 62        get_pipe_attributes,
 63        get_sync_time,
 64        pipe_exists,
 65        create_metadata,
 66        get_pipe_rowcount,
 67        drop_pipe,
 68        clear_pipe,
 69        get_pipe_columns_types,
 70        get_pipe_columns_indices,
 71        get_pipe_index_names,
 72        get_pipe_docs,
 73        get_pipe_size,
 74        compress_pipe,
 75        decompress_pipe,
 76        vacuum_pipe,
 77        analyze_pipe,
 78        partition_pipe,
 79    )
 80    from ._fetch import fetch
 81    from ._plugins import (
 82        register_plugin,
 83        install_plugin,
 84        delete_plugin,
 85        get_plugins,
 86        get_plugin_attributes,
 87        get_plugin_version,
 88    )
 89    from ._login import login, test_connection
 90    from ._users import (
 91        register_user,
 92        get_user_id,
 93        get_users,
 94        edit_user,
 95        delete_user,
 96        get_user_password_hash,
 97        get_user_type,
 98        get_user_attributes,
 99    )
100    from ._tokens import (
101        register_token,
102        get_token_model,
103        get_tokens,
104        edit_token,
105        invalidate_token,
106        get_token_scopes,
107        token_exists,
108        delete_token,
109    )
110    from ._uri import from_uri
111    from ._jobs import (
112        get_jobs,
113        get_job,
114        get_job_metadata,
115        get_job_properties,
116        get_job_exists,
117        delete_job,
118        start_job,
119        create_job,
120        stop_job,
121        pause_job,
122        get_logs,
123        get_job_stop_time,
124        monitor_logs,
125        monitor_logs_async,
126        get_job_is_blocking_on_stdin,
127        get_job_prompt_kwargs,
128        get_job_began,
129        get_job_ended,
130        get_job_paused,
131        get_job_status,
132    )
133    ### The access model (rc15): teams, permissions, access and locks are the server's. The
134    ### `InstanceConnector` methods reach its routes through the Rust client; these answer in
135    ### one request.
136    from ._access import (
137        get_team_pipes_count,
138        get_team_jobs_count,
139        get_users_permissions,
140        get_user_permissions_record,
141        get_pipe_access,
142    )
143
144    def __init__(
145        self,
146        label: Optional[str] = None,
147        wait: bool = False,
148        debug: bool = False,
149        **kw
150    ):
151        if 'uri' in kw:
152            from_uri_params = self.from_uri(kw['uri'], as_dict=True)
153            label = label or from_uri_params.get('label', None)
154            _ = from_uri_params.pop('label', None)
155            kw.update(from_uri_params)
156
157        super().__init__('api', label=label, **kw)
158        if 'protocol' not in self.__dict__:
159            self.protocol = (
160                'https' if self.__dict__.get('uri', '').startswith('https')
161                else 'http'
162            )
163
164        if 'uri' not in self.__dict__:
165            self.verify_attributes(required_attributes)
166        else:
167            conn_attrs = self.from_uri(self.__dict__['uri'], as_dict=True)
168            self.__dict__.update({
169                key: val
170                for key, val in conn_attrs.items()
171                if key not in ('label', 'protocol')
172            })
173
174        self.url = (
175            self.protocol + '://' +
176            self.host
177            + (
178                (':' + str(self.port))
179                if self.__dict__.get('port', None)
180                else ''
181            )
182        )
183        self._rust_client_obj = None
184        self._instance_keys = self.__dict__.get('instance_keys', None)
185
186    def _rust_client(self):
187        """
188        Return the `meerschaum._core.ApiConnector` of this connector (built once per process:
189        a forked child builds its own, which holds its own HTTP connections and token).
190        """
191        import os
192        attributes = {
193            key: value
194            for key, value in self.__dict__.items()
195            if not str(key).startswith('_')
196            and isinstance(value, (str, int, float, bool))
197        }
198        client = self.__dict__.get('_rust_client_obj', None)
199        if (
200            client is not None
201            and self.__dict__.get('_rust_client_pid', None) == os.getpid()
202            and self.__dict__.get('_rust_client_attributes', None) == attributes
203        ):
204            return client
205
206        ### A new process, or credentials / address changed (`conn.password = ...`): build a client
207        ### with the current attributes (it logs in again; the native flows find it by them).
208        from meerschaum import _core
209        client = _core.ApiConnector(self.label, attributes)
210        self.__dict__['_rust_client_obj'] = client
211        self.__dict__['_rust_client_pid'] = os.getpid()
212        self.__dict__['_rust_client_attributes'] = attributes
213        return client
214
215    def __getstate__(self):
216        state = self.__dict__.copy()
217        for key in ('_rust_client_obj', '_rust_client_pid', '_rust_client_attributes', '_session'):
218            state.pop(key, None)
219        state['_rust_client_obj'] = None
220        return state
221
222    def __setstate__(self, state):
223        self.__dict__.update(state)
224
225    @property
226    def URI(self) -> str:
227        """
228        Return the fully qualified URI.
229        """
230        return self._rust_client().uri
231
232    @property
233    def session(self):
234        """
235        A `requests.Session` (not used by the connector, which makes its requests in Rust).
236        It exists for plugins which still reach for it and needs `requests` installed.
237        """
238        if self.__dict__.get('_session', None) is None:
239            from meerschaum.utils.packages import attempt_import
240            requests = attempt_import('requests', lazy=False)
241            if requests is None:
242                raise ImportError("Failed to import requests. Is requests installed?")
243            self._session = requests.Session()
244        return self._session
245
246    @property
247    def token(self):
248        """
249        Return the API key, or the session token (logging in when it is missing or expiring).
250        """
251        return self._rust_client().token
252
253    @property
254    def _token(self) -> Union[str, None]:
255        """
256        The stored session token (v4's attribute): `None` before a login, and it never logs in.
257        Assign `None` to force a new login.
258        """
259        return self._rust_client().stored_token
260
261    @_token.setter
262    def _token(self, value: Union[str, None]) -> None:
263        self._rust_client().stored_token = value
264
265    @property
266    def _expires(self):
267        """
268        When the stored session token expires (v4's attribute, a naive `datetime`), or `None`.
269        """
270        import datetime
271        value = self._rust_client().stored_expires
272        return None if value is None else datetime.datetime.fromisoformat(value)
273
274    @_expires.setter
275    def _expires(self, value) -> None:
276        import datetime
277        if value is not None:
278            if isinstance(value, str):
279                value = datetime.datetime.fromisoformat(value)
280            if not isinstance(value, datetime.datetime):
281                raise TypeError(
282                    f"`_expires` must be a datetime, an ISO string or None, not {type(value).__name__}."
283                )
284            ### Stored as naive UTC, like v4's: an aware value is converted, not just stripped.
285            if value.tzinfo is not None:
286                value = value.astimezone(datetime.timezone.utc).replace(tzinfo=None)
287            value = value.isoformat()
288        self._rust_client().stored_expires = value
289
290    @property
291    def instance_keys(self) -> Union[str, None]:
292        """
293        Return the instance keys to be sent alongside pipe requests.
294        """
295        return self._instance_keys
296
297    @property
298    def login_scheme(self) -> str:
299        """
300        Return the login scheme to use based on the configured credentials.
301        """
302        return self._rust_client().login_scheme

Connect to a Meerschaum API instance.

The requests are made by the Rust client (meerschaum._core.ApiConnector, one per connector and process): the same routes, query parameters and bodies as v4. Nothing here imports requests or httpx.

APIConnector( label: str | None = None, wait: bool = False, debug: bool = False, **kw)
144    def __init__(
145        self,
146        label: Optional[str] = None,
147        wait: bool = False,
148        debug: bool = False,
149        **kw
150    ):
151        if 'uri' in kw:
152            from_uri_params = self.from_uri(kw['uri'], as_dict=True)
153            label = label or from_uri_params.get('label', None)
154            _ = from_uri_params.pop('label', None)
155            kw.update(from_uri_params)
156
157        super().__init__('api', label=label, **kw)
158        if 'protocol' not in self.__dict__:
159            self.protocol = (
160                'https' if self.__dict__.get('uri', '').startswith('https')
161                else 'http'
162            )
163
164        if 'uri' not in self.__dict__:
165            self.verify_attributes(required_attributes)
166        else:
167            conn_attrs = self.from_uri(self.__dict__['uri'], as_dict=True)
168            self.__dict__.update({
169                key: val
170                for key, val in conn_attrs.items()
171                if key not in ('label', 'protocol')
172            })
173
174        self.url = (
175            self.protocol + '://' +
176            self.host
177            + (
178                (':' + str(self.port))
179                if self.__dict__.get('port', None)
180                else ''
181            )
182        )
183        self._rust_client_obj = None
184        self._instance_keys = self.__dict__.get('instance_keys', None)

Set the given keyword arguments as attributes.

Parameters
  • type (str): The type of the connector (e.g. sql, api, plugin).
  • label (str): The label for the connector.
Examples

Run mrsm edit config and to edit connectors in the YAML file:

meerschaum:
    connections:
        {type}:
            {label}:
                ### attributes go here
IS_THREAD_SAFE: bool = False
OPTIONAL_ATTRIBUTES: List[str] = ['port', 'client_secret', 'client_id', 'api_key']
url
URI: str
225    @property
226    def URI(self) -> str:
227        """
228        Return the fully qualified URI.
229        """
230        return self._rust_client().uri

Return the fully qualified URI.

session
232    @property
233    def session(self):
234        """
235        A `requests.Session` (not used by the connector, which makes its requests in Rust).
236        It exists for plugins which still reach for it and needs `requests` installed.
237        """
238        if self.__dict__.get('_session', None) is None:
239            from meerschaum.utils.packages import attempt_import
240            requests = attempt_import('requests', lazy=False)
241            if requests is None:
242                raise ImportError("Failed to import requests. Is requests installed?")
243            self._session = requests.Session()
244        return self._session

A requests.Session (not used by the connector, which makes its requests in Rust). It exists for plugins which still reach for it and needs requests installed.

token
246    @property
247    def token(self):
248        """
249        Return the API key, or the session token (logging in when it is missing or expiring).
250        """
251        return self._rust_client().token

Return the API key, or the session token (logging in when it is missing or expiring).

instance_keys: str | None
290    @property
291    def instance_keys(self) -> Union[str, None]:
292        """
293        Return the instance keys to be sent alongside pipe requests.
294        """
295        return self._instance_keys

Return the instance keys to be sent alongside pipe requests.

login_scheme: str
297    @property
298    def login_scheme(self) -> str:
299        """
300        Return the login scheme to use based on the configured credentials.
301        """
302        return self._rust_client().login_scheme

Return the login scheme to use based on the configured credentials.

def make_request( self, method: str, r_url: str, headers: Dict[str, Any] | None = None, use_token: bool = True, debug: bool = False, **kwargs: Any) -> meerschaum._core.ApiResponse:
21def make_request(
22    self,
23    method: str,
24    r_url: str,
25    headers: Optional[Dict[str, Any]] = None,
26    use_token: bool = True,
27    debug: bool = False,
28    **kwargs: Any
29) -> 'meerschaum._core.ApiResponse':
30    """
31    Make a request to the API and return a response object
32    (`status_code`, `ok`, `text`, `content`, `headers`, `json()`, `raise_for_status()`).
33
34    Parameters
35    ----------
36    method: str
37        The name of the request method (e.g. `'GET'`, `'POST'`).
38
39    r_url: str
40        The relative URL for the request (e.g. `'/pipes'`).
41
42    headers: Optional[Dict[str, Any]], default None
43        Headers to override the default headers.
44
45    use_token: bool, default True
46        If `True`, add the authorization token to the headers.
47
48    debug: bool, default False
49        Verbosity toggle.
50
51    kwargs: Any
52        `params` (the query), `json`, `data`, `files` and `timeout` are sent as `requests` would.
53    """
54    if method.upper() not in METHODS:
55        raise ValueError(f"Method '{method}' is not supported.")
56
57    if debug:
58        dprint(f"[{self}] Sending a '{method.upper()}' request to {self.url}{r_url}")
59
60    return self._rust_client().make_request(
61        method,
62        r_url,
63        headers=headers,
64        use_token=use_token,
65        **kwargs
66    )

Make a request to the API and return a response object (status_code, ok, text, content, headers, json(), raise_for_status()).

Parameters
  • method (str): The name of the request method (e.g. 'GET', 'POST').
  • r_url (str): The relative URL for the request (e.g. '/pipes').
  • headers (Optional[Dict[str, Any]], default None): Headers to override the default headers.
  • use_token (bool, default True): If True, add the authorization token to the headers.
  • debug (bool, default False): Verbosity toggle.
  • kwargs (Any): params (the query), json, data, files and timeout are sent as requests would.
def get(self, r_url: str, **kwargs: Any) -> meerschaum._core.ApiResponse:
69def get(self, r_url: str, **kwargs: Any) -> 'meerschaum._core.ApiResponse':
70    """
71    Send a `GET` request (see `make_request()`).
72    """
73    return self.make_request('GET', r_url, **kwargs)

Send a GET request (see make_request()).

def post(self, r_url: str, **kwargs: Any) -> meerschaum._core.ApiResponse:
76def post(self, r_url: str, **kwargs: Any) -> 'meerschaum._core.ApiResponse':
77    """
78    Send a `POST` request (see `make_request()`).
79    """
80    return self.make_request('POST', r_url, **kwargs)

Send a POST request (see make_request()).

def put(self, r_url: str, **kwargs: Any) -> meerschaum._core.ApiResponse:
90def put(self, r_url: str, **kwargs: Any) -> 'meerschaum._core.ApiResponse':
91    """
92    Send a `PUT` request (see `make_request()`).
93    """
94    return self.make_request('PUT', r_url, **kwargs)

Send a PUT request (see make_request()).

def patch(self, r_url: str, **kwargs: Any) -> meerschaum._core.ApiResponse:
83def patch(self, r_url: str, **kwargs: Any) -> 'meerschaum._core.ApiResponse':
84    """
85    Send a `PATCH` request (see `make_request()`).
86    """
87    return self.make_request('PATCH', r_url, **kwargs)

Send a PATCH request (see make_request()).

def delete(self, r_url: str, **kwargs: Any) -> meerschaum._core.ApiResponse:
 97def delete(self, r_url: str, **kwargs: Any) -> 'meerschaum._core.ApiResponse':
 98    """
 99    Send a `DELETE` request (see `make_request()`).
100    """
101    return self.make_request('DELETE', r_url, **kwargs)

Send a DELETE request (see make_request()).

def wget( self, r_url: str, dest: str | pathlib.Path | None = None, headers: Dict[str, Any] | None = None, use_token: bool = True, debug: bool = False, **kw: Any) -> pathlib.Path:
104def wget(
105    self,
106    r_url: str,
107    dest: Optional[Union[str, pathlib.Path]] = None,
108    headers: Optional[Dict[str, Any]] = None,
109    use_token: bool = True,
110    debug: bool = False,
111    **kw: Any
112) -> pathlib.Path:
113    """Mimic wget: download a resource of the API (with the token) to `dest`."""
114    from meerschaum.utils.misc import wget
115    if headers is None:
116        headers = {}
117    if use_token:
118        headers.update({'Authorization': f'Bearer {self.token}'})
119    request_url = urllib.parse.urljoin(self.url, r_url)
120    if debug:
121        dprint(
122            f"[{self}] Downloading {request_url}"
123            + (f' to {dest}' if dest is not None else '')
124            + "..."
125        )
126    return wget(request_url, dest=dest, headers=headers, **kw)

Mimic wget: download a resource of the API (with the token) to dest.

def get_actions(self):
18def get_actions(self):
19    """
20    Get available actions from the API instance.
21    Returns the response (v4 does: `.json()` is the list of action names).
22    """
23    from meerschaum._internal.static import STATIC_CONFIG
24    return self.get(STATIC_CONFIG['api']['endpoints']['actions'])

Get available actions from the API instance. Returns the response (v4 does: .json() is the list of action names).

def do_action(self, sysargs: List[str]) -> Tuple[bool, str]:
27def do_action(self, sysargs: List[str]) -> SuccessTuple:
28    """
29    Execute a Meerschaum action remotely.
30    """
31    return self._rust_client().do_action(sysargs)

Execute a Meerschaum action remotely.

async def do_action_async( self, sysargs: List[str], callback_function: Callable[[str], NoneType] = functools.partial(<built-in function print>, end='')) -> Tuple[bool, str]:
34async def do_action_async(
35    self,
36    sysargs: List[str],
37    callback_function: Callable[[str], None] = partial(print, end=''),
38) -> SuccessTuple:
39    """
40    Execute an action as a temporary remote job.
41    """
42    from meerschaum.connectors.api._jobs import _LoopCallback
43    loop = asyncio.get_running_loop()
44    client = self._rust_client()
45    return await loop.run_in_executor(
46        None,
47        lambda: client.do_action(sysargs, _LoopCallback(loop, callback_function)),
48    )

Execute an action as a temporary remote job.

def do_action_legacy( self, action: List[str] | None = None, sysargs: List[str] | None = None, debug: bool = False, **kw) -> Tuple[bool, str]:
51def do_action_legacy(
52    self,
53    action: Optional[List[str]] = None,
54    sysargs: Optional[List[str]] = None,
55    debug: bool = False,
56    **kw
57) -> SuccessTuple:
58    """
59    NOTE: This method is deprecated.
60    Please use `do_action()` or `do_action_async()`.
61
62    Execute a Meerschaum action remotely.
63
64    If `sysargs` are provided, parse those instead.
65    Otherwise infer everything from keyword arguments.
66
67    Examples
68    --------
69    >>> conn = mrsm.get_connector('api:main')
70    >>> conn.do_action(['show', 'pipes'])
71    (True, "Success")
72    >>> conn.do_action(['show', 'arguments'], name='test')
73    (True, "Success")
74    """
75    if action is None:
76        action = []
77
78    if sysargs is not None and action and action[0] == '':
79        from meerschaum._internal.arguments import parse_arguments
80        json_dict = parse_arguments(sysargs)
81        action = list(json_dict.pop('action', None) or [])
82        kw = json_dict
83    else:
84        action = list(action)
85        if debug:
86            kw['debug'] = debug
87
88    return self._rust_client().do_action_legacy(action, kw)

NOTE: This method is deprecated. Please use do_action() or do_action_async().

Execute a Meerschaum action remotely.

If sysargs are provided, parse those instead. Otherwise infer everything from keyword arguments.

Examples
>>> conn = mrsm.get_connector('api:main')
>>> conn.do_action(['show', 'pipes'])
(True, "Success")
>>> conn.do_action(['show', 'arguments'], name='test')
(True, "Success")
def get_mrsm_version(self, **kw) -> str | None:
15def get_mrsm_version(self, **kw) -> Optional[str]:
16    """
17    Return the Meerschaum version of the API instance.
18    """
19    return self._rust_client().get_mrsm_version()

Return the Meerschaum version of the API instance.

def get_chaining_status(self, **kw) -> bool | None:
22def get_chaining_status(self, **kw) -> Optional[bool]:
23    """
24    Fetch the chaining status of the API instance.
25    """
26    return self._rust_client().get_chaining_status()

Fetch the chaining status of the API instance.

def get_engine_metrics(self, **kwargs):
5def get_engine_metrics(self, **kwargs):
6    """Read bounded sync and Pipeline measurements visible to this credential."""
7    from meerschaum.core.Pipeline._metrics import get_engine_metrics as read
8    return read(instance=self, **kwargs)

Read bounded sync and Pipeline measurements visible to this credential.

def initialize_engine_metrics(self):
11def initialize_engine_metrics(self):
12    """Initialize metrics storage on the served instance (full administrator only)."""
13    from meerschaum.core.Pipeline._metrics import initialize_engine_metrics as initialize
14    return initialize(instance=self)

Initialize metrics storage on the served instance (full administrator only).

def get_pipe_instance_keys(self, pipe: meerschaum.Pipe) -> str | None:
34def get_pipe_instance_keys(self, pipe: mrsm.Pipe) -> Union[str, None]:
35    """
36    Return the configured instance keys for a pipe if set,
37    else fall back to the default `instance_keys` for this `APIConnector`.
38    """
39    return self._rust_client().get_pipe_instance_keys(pipe)

Return the configured instance keys for a pipe if set, else fall back to the default instance_keys for this APIConnector.

def register_pipe( self, pipe: meerschaum.Pipe, debug: bool = False) -> Tuple[bool, str]:
42def register_pipe(
43    self,
44    pipe: mrsm.Pipe,
45    debug: bool = False,
46) -> SuccessTuple:
47    """
48    Submit a POST to the API to register a new Pipe object.
49    Returns a tuple of (success_bool, response_dict).
50    """
51    return self._rust_client().register_pipe(pipe)

Submit a POST to the API to register a new Pipe object. Returns a tuple of (success_bool, response_dict).

def fetch_pipes_keys( self, connector_keys: List[str] | None = None, metric_keys: List[str] | None = None, location_keys: List[str] | None = None, tags: List[str] | None = None, params: Dict[str, Any] | None = None, debug: bool = False) -> Dict[int | str, Tuple[str, str, str | None, Dict[str, Any]]] | List[Tuple[str, str, str | None]]:
 67def fetch_pipes_keys(
 68    self,
 69    connector_keys: Optional[List[str]] = None,
 70    metric_keys: Optional[List[str]] = None,
 71    location_keys: Optional[List[str]] = None,
 72    tags: Optional[List[str]] = None,
 73    params: Optional[Dict[str, Any]] = None,
 74    debug: bool = False
 75) -> Union[
 76    Dict[Union[int, str], Tuple[str, str, Union[str, None], Dict[str, Any]]],
 77    List[Tuple[str, str, Union[str, None]]],
 78]:
 79    """
 80    Fetch registered Pipes' keys from the API.
 81
 82    Parameters
 83    ----------
 84    connector_keys: Optional[List[str]], default None
 85        The connector keys for the query.
 86
 87    metric_keys: Optional[List[str]], default None
 88        The metric keys for the query.
 89
 90    location_keys: Optional[List[str]], default None
 91        The location keys for the query.
 92
 93    tags: Optional[List[str]], default None
 94        A list of tags for the query.
 95
 96    params: Optional[Dict[str, Any]], default None
 97        A parameters dictionary for filtering against the `pipes` table
 98        (e.g. `{'connector_keys': 'plugin:foo'}`).
 99        Not recommeded to be used.
100
101    Returns
102    -------
103    A dictionary mapping pipe IDs to key tuples, or a list of key tuples for older servers.
104    """
105    return self._rust_client().fetch_pipes_keys(
106        connector_keys,
107        metric_keys,
108        location_keys,
109        tags,
110        params,
111    )

Fetch registered Pipes' keys from the API.

Parameters
  • connector_keys (Optional[List[str]], default None): The connector keys for the query.
  • metric_keys (Optional[List[str]], default None): The metric keys for the query.
  • location_keys (Optional[List[str]], default None): The location keys for the query.
  • tags (Optional[List[str]], default None): A list of tags for the query.
  • params (Optional[Dict[str, Any]], default None): A parameters dictionary for filtering against the pipes table (e.g. {'connector_keys': 'plugin:foo'}). Not recommeded to be used.
Returns
  • A dictionary mapping pipe IDs to key tuples, or a list of key tuples for older servers.
def edit_pipe( self, pipe: meerschaum.Pipe, patch: bool = False, debug: bool = False) -> Tuple[bool, str]:
54def edit_pipe(
55    self,
56    pipe: mrsm.Pipe,
57    patch: bool = False,
58    debug: bool = False,
59) -> SuccessTuple:
60    """
61    Submit a PATCH to the API to edit an existing Pipe object.
62    Returns a tuple of (success_bool, response_dict).
63    """
64    return self._rust_client().edit_pipe(pipe, patch=patch)

Submit a PATCH to the API to edit an existing Pipe object. Returns a tuple of (success_bool, response_dict).

def sync_pipe( self, pipe: meerschaum.Pipe, df: "Optional[Union['pd.DataFrame', Dict[Any, Any], str]]" = None, chunksize: int | None = -1, debug: bool = False, **kw: Any) -> Tuple[bool, str]:
114def sync_pipe(
115    self,
116    pipe: mrsm.Pipe,
117    df: Optional[Union['pd.DataFrame', Dict[Any, Any], str]] = None,
118    chunksize: Optional[int] = -1,
119    debug: bool = False,
120    **kw: Any
121) -> SuccessTuple:
122    """
123    Sync a DataFrame (or documents, or a dictionary of lists) into a Pipe.
124    Keyword arguments are sent to the API as query parameters.
125    """
126    if isinstance(df, str):
127        df = json.loads(df)
128    return self._rust_client().sync_pipe(pipe, df, chunksize=chunksize, **kw)

Sync a DataFrame (or documents, or a dictionary of lists) into a Pipe. Keyword arguments are sent to the API as query parameters.

def delete_pipe( self, pipe: meerschaum.Pipe | None = None, debug: bool = False) -> Tuple[bool, str]:
141def delete_pipe(
142    self,
143    pipe: Optional[mrsm.Pipe] = None,
144    debug: bool = False,
145) -> SuccessTuple:
146    """Delete a Pipe and drop its table."""
147    if pipe is None:
148        raise ValueError("Pipe cannot be None.")
149    return self._rust_client().delete_pipe(pipe)

Delete a Pipe and drop its table.

def delete_pipe_cache( self, pipe: meerschaum.Pipe, debug: bool = False, **kw: Any) -> Tuple[bool, str]:
131def delete_pipe_cache(
132    self,
133    pipe: mrsm.Pipe,
134    debug: bool = False,
135    **kw: Any
136) -> SuccessTuple:
137    """Invalidate the server-side cache for a pipe."""
138    return self._rust_client().delete_pipe_cache(pipe)

Invalidate the server-side cache for a pipe.

def get_pipe_data( self, pipe: meerschaum.Pipe, select_columns: List[str] | None = None, omit_columns: List[str] | None = None, begin: str | datetime.datetime | int | None = None, end: str | datetime.datetime | int | None = None, params: Dict[str, Any] | None = None, as_chunks: bool = False, debug: bool = False, as_polars: bool | None = None, as_pandas: bool | None = None, **kw: Any) -> "Union['pd.DataFrame', None]":
152def get_pipe_data(
153    self,
154    pipe: mrsm.Pipe,
155    select_columns: Optional[List[str]] = None,
156    omit_columns: Optional[List[str]] = None,
157    begin: Union[str, datetime, int, None] = None,
158    end: Union[str, datetime, int, None] = None,
159    params: Optional[Dict[str, Any]] = None,
160    as_chunks: bool = False,
161    debug: bool = False,
162    as_polars: Optional[bool] = None,
163    as_pandas: Optional[bool] = None,
164    **kw: Any
165) -> Union['pd.DataFrame', None]:
166    """
167    Fetch data from the API, as a DataFrame of the `meerschaum:dataframe` library
168    (`as_polars=True` / `as_pandas=True` choose one).
169    """
170    from meerschaum.utils.dataframe import frame_from_arrow, frame_library_of
171    df = self._rust_client().get_pipe_data(
172        pipe,
173        select_columns=select_columns,
174        omit_columns=omit_columns,
175        begin=begin,
176        end=end,
177        params=params,
178        as_chunks=as_chunks,
179    )
180    if frame_library_of(df) is None:
181        return df
182    return frame_from_arrow(df, as_polars=as_polars, as_pandas=as_pandas)

Fetch data from the API, as a DataFrame of the meerschaum:dataframe library (as_polars=True / as_pandas=True choose one).

def get_pipe_id( self, pipe: meerschaum.Pipe, debug: bool = False) -> int | str | None:
185def get_pipe_id(
186    self,
187    pipe: mrsm.Pipe,
188    debug: bool = False,
189) -> Union[int, str, None]:
190    """Get a Pipe's ID from the API."""
191    return self._rust_client().get_pipe_id(pipe)

Get a Pipe's ID from the API.

def get_pipe_attributes( self, pipe: meerschaum.Pipe, debug: bool = False) -> Dict[str, Any]:
194def get_pipe_attributes(
195    self,
196    pipe: mrsm.Pipe,
197    debug: bool = False,
198) -> Dict[str, Any]:
199    """
200    Get a Pipe's attributes from the API.
201
202    Parameters
203    ----------
204    pipe: meerschaum.Pipe
205        The pipe whose attributes we are fetching.
206
207    Returns
208    -------
209    A dictionary of a pipe's attributes.
210    If the pipe does not exist, return an empty dictionary.
211    """
212    return self._rust_client().get_pipe_attributes(pipe)

Get a Pipe's attributes from the API.

Parameters
Returns
  • A dictionary of a pipe's attributes.
  • If the pipe does not exist, return an empty dictionary.
def get_sync_time( self, pipe: meerschaum.Pipe, params: Dict[str, Any] | None = None, newest: bool = True, debug: bool = False) -> datetime.datetime | int | None:
215def get_sync_time(
216    self,
217    pipe: mrsm.Pipe,
218    params: Optional[Dict[str, Any]] = None,
219    newest: bool = True,
220    debug: bool = False,
221) -> Union[datetime, int, None]:
222    """
223    Get a Pipe's most recent datetime value from the API.
224
225    Parameters
226    ----------
227    pipe: meerschaum.Pipe
228        The pipe to select from.
229
230    params: Optional[Dict[str, Any]], default None
231        Optional params dictionary to build the WHERE clause.
232
233    newest: bool, default True
234        If `True`, get the most recent datetime (honoring `params`).
235        If `False`, get the oldest datetime (ASC instead of DESC).
236
237    Returns
238    -------
239    The most recent (or oldest if `newest` is `False`) datetime of a pipe,
240    rounded down to the closest minute.
241    """
242    return self._rust_client().get_sync_time(pipe, params=params, newest=newest)

Get a Pipe's most recent datetime value from the API.

Parameters
  • pipe (meerschaum.Pipe): The pipe to select from.
  • params (Optional[Dict[str, Any]], default None): Optional params dictionary to build the WHERE clause.
  • newest (bool, default True): If True, get the most recent datetime (honoring params). If False, get the oldest datetime (ASC instead of DESC).
Returns
  • The most recent (or oldest if newest is False) datetime of a pipe,
  • rounded down to the closest minute.
def pipe_exists(self, pipe: meerschaum.Pipe, debug: bool = False) -> bool:
245def pipe_exists(
246    self,
247    pipe: mrsm.Pipe,
248    debug: bool = False
249) -> bool:
250    """
251    Check the API to see if a Pipe exists.
252
253    Parameters
254    ----------
255    pipe: 'meerschaum.Pipe'
256        The pipe which were are querying.
257
258    Returns
259    -------
260    A bool indicating whether a pipe's underlying table exists.
261    """
262    return self._rust_client().pipe_exists(pipe)

Check the API to see if a Pipe exists.

Parameters
Returns
  • A bool indicating whether a pipe's underlying table exists.
def create_metadata(self, debug: bool = False) -> bool:
265def create_metadata(
266    self,
267    debug: bool = False
268) -> bool:
269    """
270    Create metadata tables.
271
272    Returns
273    -------
274    A bool indicating success.
275    """
276    return self._rust_client().create_metadata()

Create metadata tables.

Returns
  • A bool indicating success.
def get_pipe_rowcount( self, pipe: meerschaum.Pipe, begin: str | datetime.datetime | int | None = None, end: str | datetime.datetime | int | None = None, params: Dict[str, Any] | None = None, remote: bool = False, debug: bool = False) -> int:
279def get_pipe_rowcount(
280    self,
281    pipe: mrsm.Pipe,
282    begin: Union[str, datetime, int, None] = None,
283    end: Union[str, datetime, int, None] = None,
284    params: Optional[Dict[str, Any]] = None,
285    remote: bool = False,
286    debug: bool = False,
287) -> int:
288    """
289    Get a pipe's row count from the API.
290
291    Parameters
292    ----------
293    pipe: 'meerschaum.Pipe':
294        The pipe whose row count we are counting.
295
296    begin: Union[str, datetime, int, None], default None
297        If provided, bound the count by this datetime.
298
299    end: Union[str, datetime, int, None], default None
300        If provided, bound the count by this datetime.
301
302    params: Optional[Dict[str, Any]], default None
303        If provided, bound the count by these parameters.
304
305    remote: bool, default False
306        If `True`, return the rowcount for the fetch definition.
307
308    Returns
309    -------
310    The number of rows in the pipe's table, bound the given parameters.
311    If the table does not exist, return 0.
312    """
313    return self._rust_client().get_pipe_rowcount(
314        pipe,
315        begin=begin,
316        end=end,
317        params=params,
318        remote=remote,
319    )

Get a pipe's row count from the API.

Parameters
  • pipe ('meerschaum.Pipe':): The pipe whose row count we are counting.
  • begin (Union[str, datetime, int, None], default None): If provided, bound the count by this datetime.
  • end (Union[str, datetime, int, None], default None): If provided, bound the count by this datetime.
  • params (Optional[Dict[str, Any]], default None): If provided, bound the count by these parameters.
  • remote (bool, default False): If True, return the rowcount for the fetch definition.
Returns
  • The number of rows in the pipe's table, bound the given parameters.
  • If the table does not exist, return 0.
def drop_pipe( self, pipe: meerschaum.Pipe, debug: bool = False) -> Tuple[bool, str]:
322def drop_pipe(
323    self,
324    pipe: mrsm.Pipe,
325    debug: bool = False
326) -> SuccessTuple:
327    """
328    Drop a pipe's table but maintain its registration.
329
330    Parameters
331    ----------
332    pipe: meerschaum.Pipe:
333        The pipe to be dropped.
334
335    Returns
336    -------
337    A success tuple (bool, str).
338    """
339    if pipe is None:
340        raise ValueError("Pipe cannot be None.")
341    return self._rust_client().drop_pipe(pipe)

Drop a pipe's table but maintain its registration.

Parameters
Returns
  • A success tuple (bool, str).
def clear_pipe( self, pipe: meerschaum.Pipe, begin: str | datetime.datetime | int | None = None, end: str | datetime.datetime | int | None = None, params: Dict[str, Any] | None = None, debug: bool = False, **kw) -> Tuple[bool, str]:
344def clear_pipe(
345    self,
346    pipe: mrsm.Pipe,
347    begin: Union[str, datetime, int, None] = None,
348    end: Union[str, datetime, int, None] = None,
349    params: Optional[Dict[str, Any]] = None,
350    debug: bool = False,
351    **kw
352) -> SuccessTuple:
353    """
354    Delete rows in a pipe's table.
355
356    Parameters
357    ----------
358    pipe: meerschaum.Pipe
359        The pipe with rows to be deleted.
360
361    begin: Union[str, datetime, int, None], default None
362        Delete rows at or after this datetime.
363
364    end: Union[str, datetime, int, None], default None
365        Delete rows before this datetime.
366
367    params: Optional[Dict[str, Any]], default None
368        Delete rows matching these parameters.
369
370    Returns
371    -------
372    A success tuple.
373    """
374    return self._rust_client().clear_pipe(pipe, begin=begin, end=end, params=params)

Delete rows in a pipe's table.

Parameters
  • pipe (meerschaum.Pipe): The pipe with rows to be deleted.
  • begin (Union[str, datetime, int, None], default None): Delete rows at or after this datetime.
  • end (Union[str, datetime, int, None], default None): Delete rows before this datetime.
  • params (Optional[Dict[str, Any]], default None): Delete rows matching these parameters.
Returns
  • A success tuple.
def get_pipe_columns_types( self, pipe: meerschaum.Pipe, debug: bool = False) -> Dict[str, str] | None:
377def get_pipe_columns_types(
378    self,
379    pipe: mrsm.Pipe,
380    debug: bool = False,
381) -> Union[Dict[str, str], None]:
382    """
383    Fetch the columns and types of the pipe's table.
384
385    Parameters
386    ----------
387    pipe: meerschaum.Pipe
388        The pipe whose columns to be queried.
389
390    Returns
391    -------
392    A dictionary mapping column names to their database types, or `None` on failure.
393    """
394    return self._rust_client().get_pipe_columns_types(pipe)

Fetch the columns and types of the pipe's table.

Parameters
Returns
  • A dictionary mapping column names to their database types, or None on failure.
def get_pipe_columns_indices( self, pipe: meerschaum.Pipe, debug: bool = False) -> Dict[str, List[Dict[str, str]]] | None:
397def get_pipe_columns_indices(
398    self,
399    pipe: mrsm.Pipe,
400    debug: bool = False,
401) -> Union[Dict[str, List[Dict[str, str]]], None]:
402    """
403    Fetch the index information for a pipe.
404
405    Parameters
406    ----------
407    pipe: mrsm.Pipe
408        The pipe whose columns to be queried.
409
410    Returns
411    -------
412    A dictionary mapping column names to lists of dictionaries with index `name` and `type`,
413    or `None` on failure.
414    """
415    return self._rust_client().get_pipe_columns_indices(pipe)

Fetch the index information for a pipe.

Parameters
  • pipe (mrsm.Pipe): The pipe whose columns to be queried.
Returns
  • A dictionary mapping column names to lists of dictionaries with index name and type,
  • or None on failure.
def get_pipe_index_names( self, pipe: meerschaum.Pipe, debug: bool = False) -> Dict[str, str]:
509def get_pipe_index_names(
510    self,
511    pipe: mrsm.Pipe,
512    debug: bool = False,
513) -> Dict[str, str]:
514    """Return the index names of a pipe's table."""
515    return self._rust_client().get_pipe_index_names(pipe)

Return the index names of a pipe's table.

def get_pipe_docs( self, pipe: meerschaum.Pipe, select_columns: List[str] | None = None, omit_columns: List[str] | None = None, begin: str | datetime.datetime | int | None = None, end: str | datetime.datetime | int | None = None, params: Dict[str, Any] | None = None, order: str = 'asc', limit: int | None = None, debug: bool = False, **kw: Any) -> List[Dict[str, Any]]:
418def get_pipe_docs(
419    self,
420    pipe: mrsm.Pipe,
421    select_columns: Optional[List[str]] = None,
422    omit_columns: Optional[List[str]] = None,
423    begin: Union[str, datetime, int, None] = None,
424    end: Union[str, datetime, int, None] = None,
425    params: Optional[Dict[str, Any]] = None,
426    order: str = 'asc',
427    limit: Optional[int] = None,
428    debug: bool = False,
429    **kw: Any
430) -> List[Dict[str, Any]]:
431    """Fetch a pipe's data as a list of documents from the API."""
432    return self._rust_client().get_pipe_docs(
433        pipe,
434        select_columns=select_columns,
435        omit_columns=omit_columns,
436        begin=begin,
437        end=end,
438        params=params,
439        order=order,
440        limit=limit,
441    )

Fetch a pipe's data as a list of documents from the API.

def get_pipe_size( self, pipe: meerschaum.Pipe, debug: bool = False, **kw: Any) -> int | None:
444def get_pipe_size(
445    self,
446    pipe: mrsm.Pipe,
447    debug: bool = False,
448    **kw: Any
449) -> Union[int, None]:
450    """
451    Return the size of the pipe's table in bytes, or `None` if it is not available.
452    """
453    return self._rust_client().get_pipe_size(pipe)

Return the size of the pipe's table in bytes, or None if it is not available.

def compress_pipe( self, pipe: meerschaum.Pipe, debug: bool = False, **kw: Any) -> Tuple[bool, str]:
456def compress_pipe(
457    self,
458    pipe: mrsm.Pipe,
459    debug: bool = False,
460    **kw: Any
461) -> SuccessTuple:
462    """Compress a pipe's table on the API instance."""
463    return self._rust_client().compress_pipe(pipe)

Compress a pipe's table on the API instance.

def decompress_pipe( self, pipe: meerschaum.Pipe, no_policy: bool = False, debug: bool = False, **kw: Any) -> Tuple[bool, str]:
466def decompress_pipe(
467    self,
468    pipe: mrsm.Pipe,
469    no_policy: bool = False,
470    debug: bool = False,
471    **kw: Any
472) -> SuccessTuple:
473    """Decompress a pipe's table on the API instance."""
474    return self._rust_client().decompress_pipe(pipe, no_policy=no_policy)

Decompress a pipe's table on the API instance.

def vacuum_pipe( self, pipe: meerschaum.Pipe, full: bool = False, debug: bool = False, **kw: Any) -> Tuple[bool, str]:
477def vacuum_pipe(
478    self,
479    pipe: mrsm.Pipe,
480    full: bool = False,
481    debug: bool = False,
482    **kw: Any
483) -> SuccessTuple:
484    """Vacuum a pipe's table on the API instance."""
485    return self._rust_client().vacuum_pipe(pipe, full=full)

Vacuum a pipe's table on the API instance.

def analyze_pipe( self, pipe: meerschaum.Pipe, debug: bool = False, **kw: Any) -> Tuple[bool, str]:
499def analyze_pipe(
500    self,
501    pipe: mrsm.Pipe,
502    debug: bool = False,
503    **kw: Any
504) -> SuccessTuple:
505    """Analyze a pipe's table on the API instance."""
506    return self._rust_client().analyze_pipe(pipe)

Analyze a pipe's table on the API instance.

def partition_pipe( self, pipe: meerschaum.Pipe, chunk_minutes: int | None = None, debug: bool = False, **kw: Any) -> Tuple[bool, str]:
488def partition_pipe(
489    self,
490    pipe: mrsm.Pipe,
491    chunk_minutes: Optional[int] = None,
492    debug: bool = False,
493    **kw: Any
494) -> SuccessTuple:
495    """Repartition a pipe's table on the API instance."""
496    return self._rust_client().partition_pipe(pipe, chunk_minutes=chunk_minutes)

Repartition a pipe's table on the API instance.

def fetch( self, pipe: meerschaum.Pipe, begin: datetime.datetime | str | int = '', end: datetime.datetime | int = None, params: Dict[str, Any] | None = None, debug: bool = False, **kw: Any) -> "Iterator['pd.DataFrame']":
19def fetch(
20    self,
21    pipe: mrsm.Pipe,
22    begin: Union[datetime, str, int] = '',
23    end: Union[datetime, int] = None,
24    params: Optional[Dict[str, Any]] = None,
25    debug: bool = False,
26    **kw: Any
27) -> Iterator['pd.DataFrame']:
28    """Get the Pipe data from the remote Pipe (`parameters['fetch']['pipe']`)."""
29    from meerschaum.config._patch import apply_patch_to_config
30    import copy
31
32    fetch_params = pipe.parameters.get('fetch', {})
33    if not fetch_params:
34        warn(f"Missing 'fetch' parameters for {pipe}.", stack=False)
35        return None
36
37    pipe_meta = dict(fetch_params.get('pipe', {}))
38    ### Legacy: check for `connector_keys`, etc. at the root.
39    if not pipe_meta:
40        ck, mk, lk = (
41            fetch_params.get('connector_keys', None),
42            fetch_params.get('metric_key', None),
43            fetch_params.get('location_key', None),
44        )
45        if not ck or not mk:
46            warn(f"Missing `fetch:pipe` keys for {pipe}.", stack=False)
47            return None
48        pipe_meta.update({
49            'connector': ck,
50            'metric': mk,
51            'location': lk,
52        })
53
54    pipe_meta['instance'] = self
55    source_pipe = mrsm.Pipe(**pipe_meta)
56    _params = copy.deepcopy(params) if params is not None else {}
57    _params = apply_patch_to_config(_params, fetch_params.get('params', {}))
58    select_columns = fetch_params.get('select_columns', [])
59    omit_columns = fetch_params.get('omit_columns', [])
60    return source_pipe.get_data(
61        select_columns=select_columns,
62        omit_columns=omit_columns,
63        begin=begin,
64        end=end,
65        params=_params,
66        debug=debug,
67        as_iterator=True,
68    )

Get the Pipe data from the remote Pipe (parameters['fetch']['pipe']).

def register_plugin( self, plugin: meerschaum.Plugin, make_archive: bool = True, debug: bool = False) -> Tuple[bool, str]:
24def register_plugin(
25    self,
26    plugin: mrsm.core.Plugin,
27    make_archive: bool = True,
28    debug: bool = False,
29) -> SuccessTuple:
30    """Register a plugin and upload its archive."""
31    import os
32    archive_path = plugin.make_tar(debug=debug) if make_archive else plugin.archive_path
33    try:
34        with open(archive_path, 'rb') as f:
35            archive = f.read()
36    except Exception:
37        return False, f"Failed to register plugin '{plugin}'."
38    return self._rust_client().register_plugin(
39        plugin,
40        archive,
41        os.path.basename(str(archive_path)),
42    )

Register a plugin and upload its archive.

def install_plugin( self, name: str, skip_deps: bool = False, force: bool = False, debug: bool = False) -> Tuple[bool, str]:
45def install_plugin(
46    self,
47    name: str,
48    skip_deps: bool = False,
49    force: bool = False,
50    debug: bool = False
51) -> SuccessTuple:
52    """Download and attempt to install a plugin from the API."""
53    import os
54    import pathlib
55    from meerschaum.core import Plugin
56    import meerschaum.config.paths as paths
57
58    dest = pathlib.Path(os.path.join(paths.PLUGINS_TEMP_RESOURCES_PATH, name + '.tar.gz'))
59    try:
60        archive = self._rust_client().download_plugin(name)
61    except ValueError as e:
62        return False, str(e)
63    dest.parent.mkdir(parents=True, exist_ok=True)
64    dest.write_bytes(archive)
65    plugin = Plugin(name, archive_path=dest, repo_connector=self)
66    return plugin.install(skip_deps=skip_deps, force=force, debug=debug)

Download and attempt to install a plugin from the API.

def delete_plugin( self, plugin: meerschaum.Plugin, debug: bool = False) -> Tuple[bool, str]:
110def delete_plugin(
111    self,
112    plugin: mrsm.core.Plugin,
113    debug: bool = False
114) -> SuccessTuple:
115    """Delete a plugin from an API repository."""
116    return self._rust_client().delete_plugin(plugin)

Delete a plugin from an API repository.

def get_plugins( self, user_id: int | None = None, search_term: str | None = None, debug: bool = False) -> List[str]:
69def get_plugins(
70    self,
71    user_id: Optional[int] = None,
72    search_term: Optional[str] = None,
73    debug: bool = False
74) -> List[str]:
75    """Return a list of registered plugin names.
76
77    Parameters
78    ----------
79    user_id: Optional[int], default None
80        If specified, return all plugins from a certain user.
81
82    search_term: Optional[str], default None
83        If specified, return plugins beginning with this string.
84
85    Returns
86    -------
87    A list of plugin names.
88    """
89    return self._rust_client().get_plugins(user_id=user_id, search_term=search_term)

Return a list of registered plugin names.

Parameters
  • user_id (Optional[int], default None): If specified, return all plugins from a certain user.
  • search_term (Optional[str], default None): If specified, return plugins beginning with this string.
Returns
  • A list of plugin names.
def get_plugin_attributes( self, plugin: meerschaum.Plugin, debug: bool = False) -> Dict[str, Any]:
92def get_plugin_attributes(
93    self,
94    plugin: mrsm.core.Plugin,
95    debug: bool = False
96) -> Dict[str, Any]:
97    """Return a plugin's attributes."""
98    return self._rust_client().get_plugin_attributes(plugin)

Return a plugin's attributes.

def get_plugin_version( self, plugin: meerschaum.Plugin, debug: bool = False) -> str | None:
101def get_plugin_version(
102    self,
103    plugin: mrsm.core.Plugin,
104    debug: bool = False
105) -> Union[str, None]:
106    """Return a plugin's version."""
107    return self._rust_client().get_plugin_version(plugin)

Return a plugin's version.

def login( self, debug: bool = False, warn: bool = True, **kw: Any) -> Tuple[bool, str]:
15def login(
16    self,
17    debug: bool = False,
18    warn: bool = True,
19    **kw: Any
20) -> SuccessTuple:
21    """Log in and set the session token."""
22    if warn and self.__dict__.get('_warn', None) is False:
23        warn = False
24    return self._rust_client().login(warn=warn)

Log in and set the session token.

def test_connection(self, **kw: Any) -> bool | None:
27def test_connection(
28    self,
29    **kw: Any
30) -> Union[bool, None]:
31    """
32    Test if a successful connection to the API may be made.
33
34    The API instance must answer `/chaining`; keyword arguments are those of `retry_connect()`:
35    `max_retries` (default 1), `retry_wait` (default 0), `warn` (default `False`),
36    `enforce_chaining` and `enforce_login` (default `False`).
37    """
38    import time
39    from meerschaum.utils.warnings import warn as _warn
40    max_retries = kw.get('max_retries', 1)
41    retry_wait = kw.get('retry_wait', 0)
42    warn = kw.get('warn', False)
43    enforce_chaining = kw.get('enforce_chaining', False)
44    enforce_login = kw.get('enforce_login', False)
45
46    try:
47        retries = 0
48        chaining_status = None
49        while retries < max_retries:
50            connected = None
51            if not isinstance(chaining_status, bool):
52                chaining_status = self.get_chaining_status()
53                if chaining_status is False:
54                    if enforce_chaining:
55                        if warn:
56                            _warn(
57                                f"Meerschaum instance '{self}' does not allow chaining "
58                                + "and cannot be used as the parent for this instance.",
59                                stack=False,
60                            )
61                        return False
62                    chaining_status = True
63
64            if chaining_status:
65                connected = self.login(warn=warn)[0] if enforce_login else True
66                if not connected and warn:
67                    _warn(f"Unable to login to '{self}'!", stack=False)
68
69            if connected:
70                return True
71
72            if warn:
73                _warn(f"Connection to '{self}' failed.", stack=False)
74            if retry_wait > 0:
75                time.sleep(retry_wait)
76            retries += 1
77        return False
78    except Exception:
79        return False

Test if a successful connection to the API may be made.

The API instance must answer /chaining; keyword arguments are those of retry_connect(): max_retries (default 1), retry_wait (default 0), warn (default False), enforce_chaining and enforce_login (default False).

def register_user( self, user: meerschaum.core.User._User.User, debug: bool = False, **kw: Any) -> Tuple[bool, str]:
37def register_user(
38    self,
39    user: mrsm.core.User,
40    debug: bool = False,
41    **kw: Any
42) -> SuccessTuple:
43    """Register a new user."""
44    return self._rust_client().register_user(user)

Register a new user.

def get_user_id( self, user: meerschaum.core.User._User.User, debug: bool = False, **kw: Any) -> int | str | uuid.UUID | None:
47def get_user_id(
48    self,
49    user: mrsm.core.User,
50    debug: bool = False,
51    **kw: Any
52) -> Union[int, str, UUID, None]:
53    """Get a user's ID."""
54    user_id = self._rust_client().get_user_id(user)
55    if isinstance(user_id, str):
56        try:
57            return UUID(user_id) if len(user_id) == 36 else user_id
58        except ValueError:
59            return user_id
60    return user_id

Get a user's ID.

def get_users(self, debug: bool = False, **kw: Any) -> List[str]:
18def get_users(
19    self,
20    debug: bool = False,
21    **kw: Any
22) -> List[str]:
23    """Return a list of registered usernames."""
24    return self._rust_client().get_users()

Return a list of registered usernames.

def edit_user( self, user: meerschaum.core.User._User.User, debug: bool = False, **kw: Any) -> Tuple[bool, str]:
27def edit_user(
28    self,
29    user: mrsm.core.User,
30    debug: bool = False,
31    **kw: Any
32) -> SuccessTuple:
33    """Edit an existing user."""
34    return self._rust_client().edit_user(user)

Edit an existing user.

def delete_user( self, user: meerschaum.core.User._User.User, debug: bool = False, **kw: Any) -> Tuple[bool, str]:
63def delete_user(
64    self,
65    user: mrsm.core.User,
66    debug: bool = False,
67    **kw: Any
68) -> SuccessTuple:
69    """Delete a user."""
70    return self._rust_client().delete_user(user)

Delete a user.

def get_user_password_hash( self, user: meerschaum.core.User._User.User, debug: bool = False, **kw: Any) -> str | None:
87def get_user_password_hash(
88    self,
89    user: mrsm.core.User,
90    debug: bool = False,
91    **kw: Any
92) -> Optional[str]:
93    """If configured, get a user's password hash."""
94    return self._rust_client().get_user_password_hash(user)

If configured, get a user's password hash.

def get_user_type( self, user: meerschaum.core.User._User.User, debug: bool = False, **kw: Any) -> str | None:
 97def get_user_type(
 98    self,
 99    user: mrsm.core.User,
100    debug: bool = False,
101    **kw: Any
102) -> Optional[str]:
103    """If configured, get a user's type."""
104    return self._rust_client().get_user_type(user)

If configured, get a user's type.

def get_user_attributes( self, user: meerschaum.core.User._User.User, debug: bool = False, **kw) -> Any:
73def get_user_attributes(
74    self,
75    user: mrsm.core.User,
76    debug: bool = False,
77    **kw
78) -> Any:
79    """Get a user's attributes."""
80    return self._rust_client().get_user_attributes(user)

Get a user's attributes.

def register_token(self, token: 'Token', debug: bool = False) -> Tuple[bool, str]:
20def register_token(self, token: 'Token', debug: bool = False) -> mrsm.SuccessTuple:
21    """
22    Register the provided token to the API.
23    """
24    return self._rust_client().register_token(token)

Register the provided token to the API.

def get_token_model( self, token_id: uuid.UUID, debug: bool = False) -> 'Union[TokenModel, None]':
27def get_token_model(self, token_id: uuid.UUID, debug: bool = False) -> 'Union[TokenModel, None]':
28    """
29    Return a token's model from the API instance.
30    """
31    from meerschaum.models import TokenModel
32    data = self._rust_client().get_token_model(_token_id(token_id))
33    if data is None:
34        return None
35    return TokenModel(**data)

Return a token's model from the API instance.

def get_tokens( self, labels: List[str] | None = None, debug: bool = False) -> List[ForwardRef('Token')]:
38def get_tokens(self, labels: Optional[List[str]] = None, debug: bool = False) -> List['Token']:
39    """
40    Return the tokens registered to the current user.
41    """
42    from meerschaum.core import Token
43    return [
44        Token(instance=self, **payload)
45        for payload in self._rust_client().get_tokens(labels)
46    ]

Return the tokens registered to the current user.

def edit_token(self, token: 'Token', debug: bool = False) -> Tuple[bool, str]:
49def edit_token(self, token: 'Token', debug: bool = False) -> mrsm.SuccessTuple:
50    """
51    Persist the token's in-memory state to the API.
52    """
53    return self._rust_client().edit_token(token)

Persist the token's in-memory state to the API.

def invalidate_token(self, token: 'Token', debug: bool = False) -> Tuple[bool, str]:
56def invalidate_token(self, token: 'Token', debug: bool = False) -> mrsm.SuccessTuple:
57    """
58    Invalidate the token, disabling it for future requests.
59    """
60    return self._rust_client().invalidate_token(token)

Invalidate the token, disabling it for future requests.

def get_token_scopes( self, token_id: uuid.UUID | ForwardRef('Token'), debug: bool = False) -> List[str]:
63def get_token_scopes(self, token_id: Union[uuid.UUID, 'Token'], debug: bool = False) -> List[str]:
64    """
65    Return the scopes for a token.
66    """
67    return self._rust_client().get_token_scopes(_token_id(token_id))

Return the scopes for a token.

def token_exists( self, token_id: uuid.UUID | ForwardRef('Token'), debug: bool = False) -> bool:
70def token_exists(self, token_id: Union[uuid.UUID, 'Token'], debug: bool = False) -> bool:
71    """
72    Return `True` if a token exists.
73    """
74    return self._rust_client().token_exists(_token_id(token_id))

Return True if a token exists.

def delete_token(self, token: 'Token', debug: bool = False) -> Tuple[bool, str]:
77def delete_token(self, token: 'Token', debug: bool = False) -> mrsm.SuccessTuple:
78    """
79    Delete the token from the API.
80    """
81    return self._rust_client().delete_token(token)

Delete the token from the API.

@classmethod
def from_uri( cls, uri: str, label: str | None = None, as_dict: bool = False) -> APIConnector | Dict[str, str | int]:
13@classmethod
14def from_uri(
15    cls,
16    uri: str,
17    label: Optional[str] = None,
18    as_dict: bool = False,
19) -> Union[
20        'meerschaum.connectors.APIConnector',
21        Dict[str, Union[str, int]],
22    ]:
23    """
24    Create a new APIConnector from a URI string.
25
26    Parameters
27    ----------
28    uri: str
29        The URI connection string.
30
31    label: Optional[str], default None
32        If provided, use this as the connector label.
33        Otherwise use the determined database name.
34
35    as_dict: bool, default False
36        If `True`, return a dictionary of the keyword arguments
37        necessary to create a new `APIConnector`, otherwise create a new object.
38
39    Returns
40    -------
41    A new APIConnector object or a dictionary of attributes (if `as_dict` is `True`).
42    """
43    from meerschaum import _core
44    params = _core.api_uri_params(uri, label)
45    return cls(**params) if not as_dict else params

Create a new APIConnector from a URI string.

Parameters
  • uri (str): The URI connection string.
  • label (Optional[str], default None): If provided, use this as the connector label. Otherwise use the determined database name.
  • as_dict (bool, default False): If True, return a dictionary of the keyword arguments necessary to create a new APIConnector, otherwise create a new object.
Returns
  • A new APIConnector object or a dictionary of attributes (if as_dict is True).
def get_jobs(self, debug: bool = False) -> Dict[str, ForwardRef('Job')]:
17def get_jobs(self, debug: bool = False) -> Dict[str, 'Job']:
18    """
19    Return a dictionary of remote jobs.
20    """
21    from meerschaum.jobs import Job
22    return {
23        name: Job(
24            name,
25            job_meta['sysargs'],
26            executor_keys=str(self),
27            _properties=job_meta['daemon']['properties'],
28        )
29        for name, job_meta in self._rust_client().get_jobs_metadata().items()
30    }

Return a dictionary of remote jobs.

def get_job(self, name: str, debug: bool = False) -> 'Job':
33def get_job(self, name: str, debug: bool = False) -> 'Job':
34    """
35    Return a single Job object.
36    """
37    from meerschaum.jobs import Job
38    metadata = self.get_job_metadata(name, debug=debug)
39    if not metadata:
40        raise ValueError(f"Job '{name}' does not exist.")
41
42    return Job(
43        name,
44        metadata['sysargs'],
45        executor_keys=str(self),
46        _properties=metadata['daemon']['properties'],
47    )

Return a single Job object.

def get_job_metadata(self, name: str, debug: bool = False) -> Dict[str, Any]:
50def get_job_metadata(self, name: str, debug: bool = False) -> Dict[str, Any]:
51    """
52    Return the metadata for a single job.
53    """
54    return self._rust_client().get_job_metadata(name)

Return the metadata for a single job.

def get_job_properties(self, name: str, debug: bool = False) -> Dict[str, Any]:
57def get_job_properties(self, name: str, debug: bool = False) -> Dict[str, Any]:
58    """
59    Return the daemon properties for a single job.
60    """
61    return self._rust_client().get_job_properties(name)

Return the daemon properties for a single job.

def get_job_exists(self, name: str, debug: bool = False) -> bool:
92def get_job_exists(self, name: str, debug: bool = False) -> bool:
93    """
94    Return whether a job exists.
95    """
96    return self._rust_client().get_job_exists(name)

Return whether a job exists.

def delete_job(self, name: str, debug: bool = False) -> Tuple[bool, str]:
 99def delete_job(self, name: str, debug: bool = False) -> SuccessTuple:
100    """
101    Delete a job.
102    """
103    return self._rust_client().delete_job(name)

Delete a job.

def start_job(self, name: str, debug: bool = False) -> Tuple[bool, str]:
106def start_job(self, name: str, debug: bool = False) -> SuccessTuple:
107    """
108    Start a job.
109    """
110    return self._rust_client().start_job(name)

Start a job.

def create_job( self, name: str, sysargs: List[str], properties: Dict[str, str] | None = None, debug: bool = False) -> Tuple[bool, str]:
113def create_job(
114    self,
115    name: str,
116    sysargs: List[str],
117    properties: Optional[Dict[str, str]] = None,
118    debug: bool = False,
119) -> SuccessTuple:
120    """
121    Create a job.
122    """
123    return self._rust_client().create_job(name, sysargs, properties)

Create a job.

def stop_job(self, name: str, debug: bool = False) -> Tuple[bool, str]:
126def stop_job(self, name: str, debug: bool = False) -> SuccessTuple:
127    """
128    Stop a job.
129    """
130    return self._rust_client().stop_job(name)

Stop a job.

def pause_job(self, name: str, debug: bool = False) -> Tuple[bool, str]:
133def pause_job(self, name: str, debug: bool = False) -> SuccessTuple:
134    """
135    Pause a job.
136    """
137    return self._rust_client().pause_job(name)

Pause a job.

def get_logs(self, name: str, debug: bool = False) -> str:
140def get_logs(self, name: str, debug: bool = False) -> str:
141    """
142    Return the logs for a job.
143    """
144    return self._rust_client().get_logs(name)

Return the logs for a job.

def get_job_stop_time(self, name: str, debug: bool = False) -> datetime.datetime | None:
147def get_job_stop_time(self, name: str, debug: bool = False) -> Union[datetime, None]:
148    """
149    Return the job's manual stop time.
150    """
151    data = self._rust_client().get_job_stop_time(name)
152    if data is None:
153        return None
154
155    return datetime.fromisoformat(data)

Return the job's manual stop time.

def monitor_logs( self, name: str, callback_function: Callable[[Any], Any], input_callback_function: Callable[[NoneType], str], stop_callback_function: Callable[[NoneType], str], stop_on_exit: bool = False, strip_timestamps: bool = False, accept_input: bool = True, debug: bool = False):
223def monitor_logs(
224    self,
225    name: str,
226    callback_function: Callable[[Any], Any],
227    input_callback_function: Callable[[None], str],
228    stop_callback_function: Callable[[None], str],
229    stop_on_exit: bool = False,
230    strip_timestamps: bool = False,
231    accept_input: bool = True,
232    debug: bool = False,
233):
234    """
235    Monitor a job's log files and execute a callback with the changes.
236    """
237    return asyncio.run(
238        self.monitor_logs_async(
239            name,
240            callback_function,
241            input_callback_function=input_callback_function,
242            stop_callback_function=stop_callback_function,
243            stop_on_exit=stop_on_exit,
244            strip_timestamps=strip_timestamps,
245            accept_input=accept_input,
246            debug=debug
247        )
248    )

Monitor a job's log files and execute a callback with the changes.

async def monitor_logs_async( self, name: str, callback_function: Callable[[Any], Any], input_callback_function: Callable[[], str], stop_callback_function: Callable[[Tuple[bool, str]], str], stop_on_exit: bool = False, strip_timestamps: bool = False, accept_input: bool = True, debug: bool = False):
176async def monitor_logs_async(
177    self,
178    name: str,
179    callback_function: Callable[[Any], Any],
180    input_callback_function: Callable[[], str],
181    stop_callback_function: Callable[[SuccessTuple], str],
182    stop_on_exit: bool = False,
183    strip_timestamps: bool = False,
184    accept_input: bool = True,
185    debug: bool = False,
186):
187    """
188    Monitor a job's log files and await a callback with the changes.
189
190    The stream runs in the Rust client; coroutine callbacks are awaited on this event loop.
191    Raise `meerschaum.jobs.StopMonitoringLogs` from a callback to stop monitoring.
192    """
193    import threading
194    loop = asyncio.get_running_loop()
195    client = self._rust_client()
196    cancelled = threading.Event()
197    try:
198        await loop.run_in_executor(
199            None,
200            lambda: client.monitor_logs(
201                name,
202                _LoopCallback(loop, callback_function),
203                input_callback_function=(
204                    _LoopCallback(loop, input_callback_function)
205                    if input_callback_function is not None
206                    else None
207                ),
208                stop_callback_function=(
209                    _LoopCallback(loop, stop_callback_function)
210                    if stop_callback_function is not None
211                    else None
212                ),
213                stop_on_exit=stop_on_exit,
214                strip_timestamps=strip_timestamps,
215                stop_event=cancelled,
216            ),
217        )
218    finally:
219        ### A cancelled task ends the stream (the worker thread checks every 100 ms).
220        cancelled.set()

Monitor a job's log files and await a callback with the changes.

The stream runs in the Rust client; coroutine callbacks are awaited on this event loop. Raise meerschaum.jobs.StopMonitoringLogs from a callback to stop monitoring.

def get_job_is_blocking_on_stdin(self, name: str, debug: bool = False) -> bool:
251def get_job_is_blocking_on_stdin(self, name: str, debug: bool = False) -> bool:
252    """
253    Return whether a remote job is blocking on stdin.
254    """
255    return self._rust_client().get_job_is_blocking_on_stdin(name)

Return whether a remote job is blocking on stdin.

def get_job_prompt_kwargs(self, name: str, debug: bool = False) -> Dict[str, Any]:
258def get_job_prompt_kwargs(self, name: str, debug: bool = False) -> Dict[str, Any]:
259    """
260    Return the kwargs to the blocking `prompt()`, if available.
261    """
262    return self._rust_client().get_job_prompt_kwargs(name)

Return the kwargs to the blocking prompt(), if available.

def get_job_began(self, name: str, debug: bool = False) -> str | None:
71def get_job_began(self, name: str, debug: bool = False) -> Union[str, None]:
72    """
73    Return a job's `began` timestamp, if it exists.
74    """
75    return self._rust_client().get_job_began(name)

Return a job's began timestamp, if it exists.

def get_job_ended(self, name: str, debug: bool = False) -> str | None:
78def get_job_ended(self, name: str, debug: bool = False) -> Union[str, None]:
79    """
80    Return a job's `ended` timestamp, if it exists.
81    """
82    return self._rust_client().get_job_ended(name)

Return a job's ended timestamp, if it exists.

def get_job_paused(self, name: str, debug: bool = False) -> str | None:
85def get_job_paused(self, name: str, debug: bool = False) -> Union[str, None]:
86    """
87    Return a job's `paused` timestamp, if it exists.
88    """
89    return self._rust_client().get_job_paused(name)

Return a job's paused timestamp, if it exists.

def get_job_status(self, name: str, debug: bool = False) -> str:
64def get_job_status(self, name: str, debug: bool = False) -> str:
65    """
66    Return the job's status.
67    """
68    return self._rust_client().get_job_status(name)

Return the job's status.

def get_team_pipes_count(self, name: str, debug: bool = False, **kwargs: Any) -> int:
42def get_team_pipes_count(self, name: str, debug: bool = False, **kwargs: Any) -> int:
43    """
44    Return how many pipes (and saved Pipelines) name a team, as far as you can see them (what
45    deleting the team gives to `Everyone`): `GET /teams/{name}?counts=true`.
46    """
47    response = self.get(f"/teams/{_segment(name)}", params={'counts': 'true'}, debug=debug)
48    return int(_body(response).get('pipes', 0) or 0)

Return how many pipes (and saved Pipelines) name a team, as far as you can see them (what deleting the team gives to Everyone): GET /teams/{name}?counts=true.

def get_team_jobs_count(self, name: str, debug: bool = False, **kwargs: Any) -> int:
51def get_team_jobs_count(self, name: str, debug: bool = False, **kwargs: Any) -> int:
52    """
53    Return how many of the server's jobs belong to a team: `GET /teams/{name}?counts=true`.
54    """
55    response = self.get(f"/teams/{_segment(name)}", params={'counts': 'true'}, debug=debug)
56    return int(_body(response).get('jobs', 0) or 0)

Return how many of the server's jobs belong to a team: GET /teams/{name}?counts=true.

def get_users_permissions(self, debug: bool = False, **kwargs: Any) -> List[Dict[str, Any]]:
59def get_users_permissions(self, debug: bool = False, **kwargs: Any) -> List[Dict[str, Any]]:
60    """
61    Return every user as the server describes them: `username`, `user_type`, `role`,
62    `permissions`, `teams` (`[{'name', 'role'}]`), `primary_team` and `email` (`None` unless it
63    is yours, you are an administrator, or you manage a team the user is in):
64    `GET /users/permissions`.
65    """
66    response = self.get('/users/permissions', debug=debug)
67    return list(_body(response).get('users', None) or [])

Return every user as the server describes them: username, user_type, role, permissions, teams ([{'name', 'role'}]), primary_team and email (None unless it is yours, you are an administrator, or you manage a team the user is in): GET /users/permissions.

def get_user_permissions_record(self, user: Any, debug: bool = False, **kwargs: Any) -> Dict[str, Any]:
70def get_user_permissions_record(
71    self,
72    user: Any,
73    debug: bool = False,
74    **kwargs: Any
75) -> Dict[str, Any]:
76    """
77    Return one user as `get_users_permissions()` describes them (`{}` for an unknown user):
78    `GET /users/{username}/permissions`.
79    """
80    username = str(getattr(user, 'username', user))
81    response = self.get(f"/users/{_segment(username)}/permissions", debug=debug)
82    if response.status_code == 404:
83        return {}
84    return dict(_body(response).get('user', None) or {})

Return one user as get_users_permissions() describes them ({} for an unknown user): GET /users/{username}/permissions.

def get_pipe_access( self, pipe: meerschaum.Pipe, debug: bool = False, **kwargs: Any) -> Dict[str, Any]:
 87def get_pipe_access(self, pipe: mrsm.Pipe, debug: bool = False, **kwargs: Any) -> Dict[str, Any]:
 88    """
 89    Return a pipe's `access` (`owner`, `team`, `visibility`, `edit`), its `locks` and what you
 90    `may` do with it (`edit`, `manage_access`, `lock`): `GET /pipes/{keys}/access`.
 91    """
 92    from meerschaum.connectors.api._pipes import pipe_r_url
 93    if pipe.connector_keys == 'mrsm' and pipe.metric_key == 'pipeline':
 94        r_url = f"/pipelines/{_segment(pipe.location_key)}/access"
 95    else:
 96        r_url = pipe_r_url(pipe) + '/access'
 97    response = self.get(
 98        r_url,
 99        params={'instance_keys': self.get_pipe_instance_keys(pipe)},
100        debug=debug,
101    )
102    body = _body(response)
103    return {key: body.get(key, None) for key in ('access', 'locks', 'may')}

Return a pipe's access (owner, team, visibility, edit), its locks and what you may do with it (edit, manage_access, lock): GET /pipes/{keys}/access.