Meerschaum 3¶
Historical release notes for the 3.x series.
3.5.0 Releases¶
Read release notes
v3.5.2¶
-
Fix a stale schema cache surviving
DELETE /pipes/{ck}/{mk}/{lk}/cache. A pipe's column types are cached in memory only, butPipe._invalidate_cache(hard=True)discovered the keys to clear by listing what had been written to disk (or to the cache connector). For a pipe withcache=False(every pipe the API server holds) that list was empty, so a hard invalidation cleared nothing and the route which exists to refresh the schema after a schema-changing sync was a no-op. An API client which added a column and read the pipe back could receive the previous column list for up to a minute. The in-memory schema keys are now cleared explicitly. -
Scale an explicit chunk interval to an integer datetime axis.
Pipe.get_chunk_interval()applied the pipe'sprecisiononly when resolving the interval from theverifyparameters, never when given one directly.repartition(andpartition pipes --chunk-minutes) passes its width through that path, so asking for 30-day chunks on a millisecond axis set a chunk interval of 43,200 milliseconds (about 43 seconds) while reporting success and persistingverify.chunk_minutesas 43200. Anintargument is still taken verbatim as the axis's own epoch units; atimedeltais now scaled byprecision, and the callers which mean minutes pass atimedelta.repartitionreports an integer axis's new width in epoch units. -
Register an
integer_nowfunction for integer-axis hypertables. TimescaleDB cannot evaluate "older than N" for a columnstore policy on a hypertable partitioned by an integer column until such a function exists. Meerschaum never calledset_integer_now_func, so the policy was accepted at creation and then failed on every background run withinteger_now function not set on hypertable, compression silently never happened, on exactly the tables which grow largest. Amrsm_integer_now_<unit>()function derived from the pipe'sprecisionis now registered before any columnstore policy is installed, including the one TimescaleDB auto-creates for a Hypercore table atCREATE TABLE. -
Honor a duration
compress:afteron an integer datetime axis.compress: {after: '30 days'}(the documented spelling on a datetime axis) was discarded on an integer axis and replaced with a hard-coded 7-day equivalent, with no warning and with the configured value still visible in the pipe's parameters. Durations are now parsed and scaled by the axisprecision, so the same spelling means the same thing on both axis types. A bare number is still used verbatim as epoch units, and a value which cannot be parsed warns and names the fallback rather than silently substituting a different retention. -
Fix
/logindropping a user's custom scopes. The password grant read scopes from a freshly constructedUserwithout refreshing its attributes from the instance, so any scopes stored in a user's attributes were ignored and the issued JWT silently carried the default scope set, routes gated on a custom scope returned 403 with valid credentials. Separately, an omittedscopeform field was treated as an explicit request for every default scope, which filtered out anything beyond that set; per RFC 6749 ยง 3.3, an omittedscopenow grants the principal's allowed scopes (and['*']for all-scope tokens). -
Probe the cache connector before using it.
get_cache_connector()memoized the configured connector without ever contacting the server, so a misconfigured or unreachable cache looked exactly like "no cache" while every cache operation failed at the call site. The connector is now pinged when first resolved; on failure the API warns, continues without a cache (the documentedNonepath), and probes again after 60 seconds so a cache which is merely slow to start isn't disabled for the life of the process. -
Restrict the served routes with
api:permissions:routes:allowlist. Serve a minimal API surface (e.g. only the routes added by your API plugins) by listing which groups of core routes to register (fnmatchpatterns againstactions,connectors,index,jobs,misc,pipes,plugins,tokens,users,version,mcp, andwebterm; the default['*']serves everything). Disallowed groups are never registered, rather than removed from the app after the fact./loginis always served so that issued tokens keep authenticating plugin routes, and the web console registers its routes independently (start with--no-dashfor a restricted surface).
# Under api:permissions, serve only `/version` alongside plugin routes.
routes:
allowlist:
- 'version'
v3.5.1¶
- Fix upserting rows which null out
json,numeric, oruuidcolumns. Patching a row so that a special-dtype column becomesNULLfailed on PostgreSQL-like flavors with an error such ascolumn "j" is of type jsonb but expression is of type text. An all-null column carries no values to inspect, so the temporary staging table created it asTEXTinstead of the column's real type. Staged columns now fall back to the pipe's registered dtype when every incoming value is null.
import meerschaum as mrsm
pipe = mrsm.Pipe(
'test', 'upsert', 'nulls',
instance='sql:main',
columns={'primary': 'id'},
upsert=True,
dtypes={'id': 'int', 'j': 'json'},
)
pipe.sync([{'id': 1, 'j': [1, 2]}])
pipe.sync([{'id': 1, 'j': None}])
v3.5.0¶
- Add a first-class MCP server.
AI assistants can now discover your pipes, read their data, and run actions, 15 tools, 5 resources, and 3 prompts. Run it locally over stdio with
mrsm start mcp, or remotely at an API instance's/mcpendpoint (where each tool requires the same scope as its REST equivalent, and tools your token can't call are hidden).
This replaces the community mcp plugin, uninstall it with mrsm uninstall plugin mcp. Read MCP security before exposing /mcp: api:mcp:read_only hides every modifying tool, execute_action denies the code-executing actions by default, and read_sql accepts only a single read-only statement under its own sql:read scope.
The MCP tools enforce the same guardrails as the REST routes: pipes targeting the protected instance tables (mrsm_users, mrsm_tokens, mrsm_pipes, mrsm_plugins) can't be registered, read, or written; clear_pipe and deduplicate_pipe require pipes:delete; instance_keys is checked against the API's allowed instances; and execute_action applies the server's action permissions, pins the instance, and refuses a denied action passed as another action's subaction. Tool calls run off the event loop, so a long sync no longer stalls other requests.
-
Reject
jsoncolumns used as index columns. JSON values can't be sorted or deduplicated, so such a pipe synced once and then failed withTypeError: unhashable type: 'dict'. Registering, editing, or syncing now fails immediately instead. Useindicesif you only want a database index on the column. See Data Types. -
Require authentication for web console actions which read or write data. Twelve Dash callbacks (pipe data, parameter and SQL edits, syncing documents, the users listing, job logs, and tokens) could be triggered without a session by posting to the callback endpoint directly.
-
Fix
--no-authrejecting every authenticated route.mrsm start api --no-authreturned401 Not authenticatedfor all endpoints instead of skipping authentication. -
Require authentication to list API tokens in the web console. The tokens page's refresh callback could be triggered without a session, returning every token's client ID, label, and expiration.
-
Fix the web console minting tokens with all scopes. Deselecting "Grant all scopes" and choosing individual scopes had no effect, the registered token always carried every scope.
-
Enforce the registration permission in the web console. With
api:permissions:registration:usersdisabled, the web console's register form still created accounts; only the REST route checked it. -
Add Plotly Dash's MCP server for plugin authors. Set
api:dash:mcp:enabled(requiresdash>=4.3.0) to expose the web console's layout at/dash/_mcp, so an assistant can inspect the component tree while you write a web page plugin. Off by default because it's served without authentication. See Web Console.
3.4.0 Releases¶
Read release notes
v3.4.7¶
-
Reduce CPU usage while watching job logs. Rich console formatting is now reused across log lines instead of being rebuilt for every line.
-
Clear the plugins update cache after upgrading plugins.
upgrade pluginsnow removes its cached remote-version results after a successful install, so the next shell does not show an outdated upgrade notice. -
Re-sync plugins symlinks after
clear cache. The actionclear cachenow delete and rebuilds internal plugins symlinks (clearing out symlinked plugins from other plugins dirs). -
Move plugins update cache to
.internal/cache. The plugins update checker files now reside within.internal/cache.
v3.4.6¶
- Fix venvs path not following
set_root(). WhenMRSM_VENVS_DIRis unset,VIRTENV_RESOURCES_PATHnow derives fromROOT_DIR_PATHdynamically, so in-process root swaps (e.g.composerunning custom actions underreplace_root_dir()) activate venvs from{root}/venvs, matching where subprocess-mode commands likeinstall requiredinstall packages. Previously the path was frozen at import time, so custom plugin actions run throughcomposecould not import their ownrequireddependencies.
v3.4.5¶
-
Fix a breaking bug in
bootstrap plugin. A bug inpackage_venv()has been fixed, unblockingbootstrap plugin. -
Fix warning bug on shell launch. The plugin updater thread was throwing a login warning for
api:mrsm; this warning has been surpressed. -
Require
importlib_metadatawhen starting the API. Somedashdependencies require the legacyimportlib_metadata, so this behavior has been accounted for. -
Fix plugins environment scope for
compose. Thepluginsnamespace scope has been fixed to harden the isolation ofcompose.
v3.4.3 โ v3.4.4¶
-
Add a plugin version API endpoint. The endpoint
/plugins/{name}/versionnow returns a plugin's version string. -
Hide login warning on plugin updater thread. The plugins updater thread no longer will raise a login warning.
-
Add precision kwarg aliases to
get_current_timestamp(). You may now treatPipe.precisionas kwargs forget_current_timestamp():
import meerschaum as mrsm
from meerschaum.utils.dtypes import get_current_timestamp
pipe = mrsm.Pipe(
'demo', 'precision', 'timestamp', instance='sql:local',
precision={'unit': 's', 'interval': 30},
)
now = get_current_timestamp(**pipe.precision)
print(now)
# 2026-06-23 04:30:30+00:00
- Sync plugins symlinks on daemon fork. To mitigate isolation issues with Meerschaum Compose, Daemons now sync plugins' symlinks before forking.
v3.4.2¶
-
Run inline Python with
mrsm python -c. Thepythonaction now accepts Python's own-c CODEidiom (e.g.mrsm python -c "print(mrsm.__version__)"), executing the code withmeerschaumalready imported and then exiting. Globally-cmeans--connector-keys, so this rewrite is scoped to thepythonaction only; every other action's-cis unchanged. The previous forms (mrsm python "code" --noaskand the bracketedmrsm python [-c "code"]) still work. -
Fix truncated output for actions run on a remote
api:executor. Running a long-output action (e.g.show pipes) on anapi:executor only showed the last 30 lines, the beginning was silently dropped. The remote job's log monitor defaults to emitting only the lastlines_to_show(30) lines, which is the right behavior when attaching to a long-running job but wrong for a one-shot action whose entire output matters. Temporary action jobs created byAPIConnector.do_action_asyncnow setlogs.lines_to_show = 0(emit everything), mirroring the local CLI worker. -
Add a Users page to the web console. A new
/dash/userspage lists the instance's registered users (paginated and searchable, mirroring the Plugins page), with a shareable per-user detail page at/dash/users/<username>that lists the plugins they've published. A plugin card's author now links to its owner's user page. A user's type (admin vs. normal) is intentionally never exposed. -
Format
show dataoutput as a table.mrsm show datanow renders each pipe's DataFrame as a Markdown table (the samepprint_df()formatter used bymrsm sql) instead of the raw Pandas repr. The--noprettyJSON output is unchanged. -
Add a plugins update checker. Plugins installed from a repository now record their origin (e.g.
api:mrsm) in a per-directory.mrsm_origins.jsonmanifest that travels with the plugins directory (so it survives being mounted into another environment). On shell launch a background thread caches the latest versions from each plugin's origin repo, and the interactive prompt prints a banner listing any plugins that have fallen behind, suggestingupgrade plugins. Locally-authored plugins (no recorded origin) are skipped. Reuses the existingshell:updatesconfig (check_remote,refresh_minutes). -
Silence the
starlette.middleware.wsgideprecation warning duringstart api. The Dash app is now mounted witha2wsgi'sWSGIMiddlewareinstead of the deprecatedfastapi.middleware.wsgi.WSGIMiddleware.
v3.4.1¶
- Scope the
dbc_darktheme so it no longer leaks onto every Dash page. Several rule blocks indbc_dark.css(.form-control, the.dash-dropdown-*family, and.dash-options-list) were unscoped, so theirbackground-color: var(--bs-dark) !importantdeclarations applied to every Dash app served by the API, forcing plugins to fight each one with higher-specificity!importantoverrides. These blocks are now scoped under.dbc_dark, making the dark theme opt-in.
To keep the web console (including its body-portaled dropdown menus) unchanged, the Dash app now sets class="dbc_dark" on <body> by default.
-
Add a light theme for plugin pages via
@web_page(dark_theme=False). Plugin pages render with the console's dark theme by default. A page can now opt into a light theme with@web_page(dark_theme=False): the Web Console loads both the dark (Bootswatch Darkly) and light (Flatly) Bootstrap themes and enables exactly one per route, switching to light (and dropping thedbc_darkclass) while an opted-out page is active, and back to dark for the console and every other page, including on in-app navigation. See Styling Your Page. -
Fix starting jobs whose target reaches unpicklable state (e.g. an
asyncio.Task). Starting a job (e.g.sync pipes ... --loop --name foo) could fail withTypeError: cannot pickle '_asyncio.Task' objectwhen a connector cached a live async task. The daemon pickled its target function by value, dragging in the target's global graph; that walk reached the unpicklable state. Importable, top-level targets are now pickled by reference (re-imported in the daemon process), so the global graph is never serialized. Closures still pickle by value, unchanged. -
Add a Termux-style on-screen key row to the web console terminal (mobile). On small screens the Webterm now shows a row of keys above the terminal,
ESC,CTRL,SHIFT,TAB, and arrow keys laid out like a physical keyboard.CTRL/SHIFTare sticky modifiers that apply to the next keypress (tap again to toggle off), and tapping a key keeps the soft keyboard open. The row is hidden on larger screens with a physical keyboard. -
Web console UX and accessibility improvements.
- Loading spinners while pipes, jobs, and tokens load.
- A confirmation dialog before deleting a job.
- The tokens table is now horizontally scrollable on small screens.
- Tooltips/labels on icon-only buttons and
alttext on the logo and banner images. - Errors that were previously swallowed (CSV download, a pipe's columns / recent-data accordion) now surface their message.
-
Fix the web console fullscreen terminal rendering at half width. Toggling the Webterm's fullscreen button clobbered the column's responsive classes and left the terminal narrow without refitting. Fullscreen now preserves the responsive layout and the terminal refits to fill the width.
-
Fix the web console "Plugins" button raising a
ValueError. Clicking Plugins in the dashboard raisedtoo many values to unpackafter the plugins view started returning pagination metadata. The dashboard now ignores the extra trailing values.
v3.4.0¶
- Accept datetime bounds on epoch integer-axis pipes.
Actions like
show data,sync pipes,clear pipes, anddeduplicate pipesnow accept a datetime--begin/--endfor pipes whose datetime axis is an integer epoch. The bound is translated to the axis's integer value using the pipe'sprecision, so a single command can target both datetime- and integer-axis pipes at once.
# The datetime is translated to an epoch (e.g. milliseconds) for the integer 'ts' axis.
mrsm show data --begin 2026-05-30 -c mqtt:acme -m test -l backup -i sql:main
Integer bounds (--begin 1780099200000) still pass through unchanged. A datetime bound on a non-epoch integer axis (no precision set) now raises a clear error instead of producing an invalid query.
-
Add
datetime_to_int()tomeerschaum.utils.dtypes. Convert adatetimeto an integer epoch at a given precision unit (e.g.'millisecond'). Naive datetimes are treated as UTC and integers pass through unchanged.get_current_timestamp(..., as_int=True)now reuses this helper. -
Add disk usage and compression tools. New tooling helps manage how much space pipes occupy on disk:
-
Add the
show sizesaction. Lists pipes with each target table's on-disk size, sorted largest first. Sizes are fetched in parallel when the instance connector is thread-safe (otherwise serially).show rowcountsnow also sorts largest first. -
Add
Pipe.get_size(). Returns a target table's on-disk size in bytes (orNoneif unavailable). Backed by the new instance-connector methodget_pipe_size(), with per-flavor support for SQL connectors, TimescaleDB hypertables usehypertable_size(), PostgreSQL-like flavors usepg_total_relation_size(), and MySQL/MariaDB, MSSQL, and SQLite use their respective size queries. -
Add the
compress pipesaction andPipe.compress(). Compress pipes' target tables to reclaim disk space. For TimescaleDB this enables native compression, installs a compression policy, and compresses existing chunks; MySQL/MariaDB and MSSQL use their native table compression. Backed by the new instance-connector methodcompress_pipe(). -
Add the
decompress pipesaction andPipe.decompress(). The inverse ofcompress pipes. For TimescaleDB this removes the compression policy, converts compressed chunks back to row-store, and disables the columnstore so future synced chunks stay uncompressed; MySQL/MariaDB and MSSQL revert their native table compression. Pass--no-policyto decompress existing chunks now while leaving the policy in place (e.g. for a bulk backfill, after which chunks are recompressed on schedule). Backed by the new instance-connector methoddecompress_pipe(). -
Add the
vacuum pipesandanalyze pipesactions (andPipe.vacuum()/Pipe.analyze()).vacuum pipesreclaims disk space from dead rows (--fullrunsVACUUM FULLon PostgreSQL-family flavors; MySQL/MariaDB useOPTIMIZE TABLE, MSSQL rebuilds indices, SQLite runsVACUUM).analyze pipesrefreshes the query planner's statistics without reclaiming space. Backed by the new instance-connector methodsvacuum_pipe()andanalyze_pipe(). See Maintenance. -
Add the
show sizescompanionshow partitionsaction. Lists each partitioned pipe's partition (or TimescaleDB chunk) count and physical width. Backed by the new instance-connector methodget_partition_info(). -
Add the
compresspipe parameter. Setcompress(aboolor a dictionary ofafter/segmentby/orderbysettings) to mark a pipe for compression. For TimescaleDB hypertables, a compression policy is installed automatically on sync.
import meerschaum as mrsm pipe = mrsm.Pipe( 'demo', 'compress', instance='sql:main', columns={'datetime': 'ts', 'id': 'station'}, compress={'after': '7 days'}, )compress pipes --no-policyalso accepts--no-policy, to compress existing chunks immediately without installing an ongoing policy.- Add the
hypercorepipe parameter (TimescaleDB).hypercore(defaultTrue) enables the Hypercore columnstore atCREATE TABLE(declaringtsdb.segmentby/tsdb.orderby), so TimescaleDB auto-creates a columnstore policy that converts old chunks in the background. SethypercoretoFalsefor a plain row-store hypertable. The columnstore policy is the compression policy,add_columnstore_policyis the modern equivalent of the legacyadd_compression_policy.
-
-
Add native range partitioning for non-TimescaleDB flavors. TimescaleDB auto-creates chunks on insert; PostgreSQL / PostGIS, MySQL / MariaDB, and MSSQL do not. The
hypertableparameter (which defaults toTrue) now drives declarative range partitioning on the pipe'sdatetimecolumn for these flavors too (so datetime-axis pipes are partitioned by default; sethypertabletoFalseto opt out. Only newly created tables are affected (pre-existing plain tables are never retroactively partitioned). The partition width reuses the chunk interval (verify.chunk_minutes, default 43200) 30 days), and boundaries are epoch-aligned so the same value always maps to the same partition (deterministic, value-independent). A single sync creates at mostsystem.connectors.sql.instance.max_partitions_per_syncpartitions (default 10,000) as a runaway guard.
import meerschaum as mrsm
pipe = mrsm.Pipe(
'demo', 'partition',
instance='sql:main', # a PostgreSQL/MySQL/MSSQL connector
columns={'datetime': 'ts', 'id': 'station'},
parameters={
'hypertable': True,
'verify': {'chunk_minutes': 43200}, # 30-day partitions (default)
},
)
The parent table is created with the flavor's range-partitioning DDL (PARTITION BY RANGE for PostgreSQL, PARTITION BY RANGE COLUMNS for MySQL / MariaDB, a partition function + scheme for MSSQL), and the interval-aligned child partitions a dataframe needs are auto-created in sync_pipe before its rows are inserted. The partition column is folded into the primary key as required by each flavor. Pipe.verify() aligns its chunk edges to the same epoch-anchored grid (get_chunk_bounds(align=True)) so verification chunks coincide with partition boundaries. See SQL Connectors โ Native Range Partitioning for the per-flavor details.
verify.chunk_minutes is the authoritative partition width, it is read at sync time, so editing it for a populated table can create misaligned, overlapping partitions. Use the new partition pipes action (below) to change an existing table's width.
-
Expand the
verifychunk-interval keys. The chunk interval (used both for verification syncs and as the native partition width) can now be set with whichever unit reads most naturally, instead of always converting to minutes.- Unit aliases for
verify.chunk_minutes.chunk_hours,chunk_days,chunk_weeks,chunk_years, andchunk_secondsjoinchunk_minutes. If several are set, the first on this priority list wins (mirroring the existingbound_*keys):chunk_minutes,chunk_hours,chunk_days,chunk_weeks,chunk_years,chunk_seconds. The default is unchanged (chunk_minutes: 43200, 30 days).
import meerschaum as mrsm # These two pipes use the same one-week chunk interval. mrsm.Pipe('demo', 'minutes', parameters={'verify': {'chunk_minutes': 10080}}) mrsm.Pipe('demo', 'days', parameters={'verify': {'chunk_days': 7}})- Add
verify.chunk_rangefor integerdatetimeaxes. For a pipe whosedatetimeaxis is an integer epoch,chunk_rangesets the chunk size directly in the axis's own units (used verbatim), the integer-axis counterpart tochunk_minutes.
import meerschaum as mrsm pipe = mrsm.Pipe( 'demo', 'int_axis', columns={'datetime': 'ts'}, dtypes={'ts': 'int'}, parameters={'verify': {'chunk_range': 1000}}, # 1000 units per chunk )When
chunk_rangeis unset, the legacy behavior is preserved: the time-based size is converted to the axis's units via the pipe'sprecision, or (when noprecisionis set) its value in minutes is used verbatim. - Unit aliases for
The --chunk-minutes flag and an explicit chunk_interval argument to Pipe.get_chunk_interval() still override the configured keys.
- Add the
partition pipesaction andPipe.repartition(). Rebuild a partitioned pipe's target table to a new chunk width. Pass--chunk-minutes(defaults to the pipe'sverify.chunk_minutes). On TimescaleDB this callsset_chunk_time_interval()(applies to future chunks; existing chunks keep their size); on PostgreSQL / PostGIS, MySQL / MariaDB, and MSSQL the table is rebuilt at the new width (read โ drop โ re-sync) andverify.chunk_minutesis updated. Backed by the new instance-connector methodpartition_pipe().
# Repartition a pipe's table to 7-day chunks.
mrsm partition pipes -i sql:main -m weather --chunk-minutes 10080
!!! warning The non-TimescaleDB rebuild reads the whole table into memory and briefly drops it before re-syncing. Run it during a maintenance window for large tables.
-
Wire the maintenance actions through
APIConnector.APIConnectornow implementsget_pipe_size,compress_pipe,decompress_pipe,vacuum_pipe,analyze_pipe, andpartition_pipe(with matching/size,/compress,/decompress,/vacuum,/analyze, and/partitionroutes), soshow sizes,compress/decompress,vacuum/analyze, andpartitionall work againstapi:instances (previously these reported "not supported" over the API). -
Print full, untruncated results from
mrsm sql. Reading a table or query withsql ... readnow prints the entire DataFrame as a Markdown table, no more...column or cell truncation. The format is friendly to both users and LLMs and ends with a[rows x columns]shape footer. The--noprettyJSON output is unchanged. -
Back up configuration files before editing. Running
edit confignow snapshots the existing file before opening your editor. Backups are kept underMRSM_ROOT_DIR/.internal/config_backups/<key>/(the ten most recent per key). -
Add
edit config --rollback. Restore a configuration key from its most recent backup instead of opening the editor. The current file is itself backed up first, so a rollback can always be undone.
-
Stop overwriting config files with defaults. A malformed configuration file (e.g. from a bad manual edit) is no longer silently overwritten with default values. Meerschaum now falls back to defaults in memory, leaves the file on disk untouched, and points you to
edit config <key> --rollback. Brand-new config files are still populated on upgrade. -
Fix
stop jobsleaving zombie sync threads. Stopping a job now reliably terminatessync pipesworker threads. Previously the daemon's main thread could exit while non-daemon worker threads kept running and appending to the log, requiring a container restart. Workers now honor a process-wide stop signal and are interrupted when a job is stopped. -
Fix
get_sync_time()withparamson partitioned MariaDB tables. Because datetime-axis pipes are now range-partitioned by default,get_sync_time(params=...)could returnNonefor a populated MariaDB pipe, a MariaDB optimizer quirk whereORDER BY <dt> DESC LIMIT 1over aRANGE COLUMNStable with aWHEREclause stops scanning early. Partitioned MariaDB pipes now compute the bound withMAX()/MIN(), which prunes partitions correctly. Other flavors are unaffected. -
Fix native partitioning for
date-axis and distant-datetime pipes. Two partition-boundary edge cases no longer error on sync. (1) A pipe whosedatetimecolumn has adatedtype (rather than a timestamp) raisedTypeError: int() argument must be ... not 'datetime.date'; the boundary helpers now promote adateto a midnightdatetimeso it follows the timestamp grid instead of the integer-epoch path. (2) A very wide datetime span combined with a large chunk interval could push a computed boundary pastdatetime.min/datetime.maxand raiseOverflowError: date value out of range; boundaries are now clamped to the representable range. Affects PostgreSQL / PostGIS, MySQL / MariaDB, and MSSQL.
3.3.0 Releases¶
Read release notes
v3.3.1¶
-
Add
--targets/--target. Similar to--tags, the--targetflag filters pipes based on the target table name. Values may be negated. -
Add
show targets. The actionshow targetsprints a table of pipes and their target tables and schemata. -
Fix
--dtypefiltering on symlinked pipes. Pipes which symlink their dtypes are now correctly handled by the--dtypefilter. -
Fix stale plugins bug. A bug with plugin loading and unloading has been fixed.
-
Update plugins page. The Web Console plugins page has been improved.
v3.3.0¶
- Add
--dtypeflag. The flag--dtype(explicitly--datetime-dtypesor--datetime-dtype) allows filtering pipes based on the data type of thedatetimeaxis. Accepted values aredatetime,int,None. This behaves similarly to keys selectors: multiple values may be provided, and values may be negated:
- Improve
copy pipesflow. Thecopy pipesaction has been significantly improved for both interactive and scripted use cases.
Non-interactive batch copying:
Specify the destination instance as the first positional argument to skip all prompts. Combined with --force and --sync-data, pipes can now be fully scripted:
# Copy pipe definitions only (skip existing pipes)
mrsm copy pipes sql:dest -i sql:src
# Overwrite existing pipes and copy all data
mrsm copy pipes sql:dest -i sql:src --force --sync-data all
# Overwrite and sync with a date filter (--begin/--end imply filter mode automatically)
mrsm copy pipes sql:dest -i sql:src --force --begin 2024-01-01
New --sync-data flag:
Controls what data is synced when copying destination pipes. Accepted values are nothing (default), backtrack, all, and filter. When --begin, --end, or --params are provided without --sync-data, filter mode is selected automatically.
Reduced interactive prompts for batches: When copying many pipes to the same destination instance, all choices (conflict resolution, data sync mode, filter flags) are now collected upfront before any copying begins.
Granular conflict resolution:
When destination pipes already exist, the interactive prompt now offers three options rather than a simple yes/no:
- Skip, leave the destination pipe entirely unchanged.
- Data only, sync data without updating stored attributes.
- Attributes only, update attributes without syncing data.
- Attributes and data, full overwrite (equivalent to --force).
Copy summary table: After copying data, a table is printed showing inserted, updated, and upserted row counts alongside the elapsed time per pipe and totals.
-
Improve caching performance. Better cache handling now reduces round-trips when fetching pipes' metadata.
-
Allow dictionary return value from
fetch_pipes_keys(). TheInstanceConnectormethodfetch_pipes_keys()may now return a dictionary, where the indices are pipes' IDs and the values are keys tuples (and possibly parameters or tags).
import json
import meerschaum as mrsm
from meerschaum.utils import fetch_pipes_keys
conn = mrsm.get_connector('sql:main')
keys_dict = fetch_pipes_keys('registered', mrsm.get_connector(), connector_keys=['sql:main'], metric_keys=['test'])
print(json.dumps(keys_dict, indent=4))
# {
# "24": [
# "sql:main",
# "test",
# null,
# {
# "sql": "SELECT *\nFROM {{ Pipe('plugin:noaa', 'weather') }}\nWHERE 1 = 1",
# "tags": [
# "test"
# ],
# "fetch": {
# "backtrack_minutes": 1440
# },
# "verify": {
# "bound_days": 366,
# "chunk_minutes": 43200
# },
# "columns": {
# "id": null,
# "datetime": null
# }
# }
# ]
# }
- Add
Pipe.get_docs(). The convenience methodPipe.get_docs()returns a pipe's data as a list of dictionaries, bypassing pandas overhead. Combine withas_chunks=Trueto get an iterator ofList[Dict]chunked by time bounds:
import meerschaum as mrsm
pipe = mrsm.Pipe(
'demo', 'docs',
instance='sql:memory',
temporary=True,
columns={'primary': 'id'},
)
pipe.sync([
{'id': 1, 'name': 'Alice'},
{'id': 2, 'name': 'Bob'},
])
# All rows as a list of dicts (no pandas overhead):
docs = pipe.get_docs()
print(docs)
# [{'id': 1, 'name': 'Alice'}, {'id': 2, 'name': 'Bob'}]
# Single row as a dict:
doc = pipe.get_doc(params={'id': 2})
print(doc)
# {'id': 2, 'name': 'Bob'}
# Chunked iterator of lists of dicts:
for chunk in pipe.get_docs(as_chunks=True):
print(chunk)
- Fix shell suggestions. A bug has been fixed where shell actions were truncated when chaining actions. Additionally, actions suggestions have been updated with highlight colors.
3.2.0 Releases¶
Read release notes
v3.2.4¶
- Improve default chunksize for integer pipes.
Pipes with integer datetime columns now default to appropriately sized chunks based on the
precisionunit.
import meerschaum as mrsm
pipe = mrsm.Pipe(
'demo', 'chunksize', 'int',
instance='sql:memory',
parameters={
"columns": {
"datetime": "ts",
"id": "id",
},
"dtypes": {
"ts": "int",
},
"precision": "ms", # millisecond
"fetch": {
"backtrack_minutes": 1440,
},
"verify": {
"chunk_minutes": 1440 * 30, # 30 days
}
},
)
print(f"{pipe.get_chunk_interval()=}")
# pipe.get_chunk_interval()=2592000000
print(f"{pipe.get_backtrack_interval()=}")
# pipe.get_backtrack_interval()=86400000
pipe.precision = 's'
print(f"{pipe.get_chunk_interval()=}")
# pipe.get_chunk_interval()=2592000
print(f"{pipe.get_backtrack_interval()=}")
# pipe.get_backtrack_interval()=86400
simple_pipe = mrsm.Pipe(
'demo', 'chunksize', 'int_no_precision',
instance='sql:memory',
parameters={
'columns': {
'datetime': 'id',
},
'dtypes': {
'id': 'int',
},
"precision": None, # omitted precision defaults to literal chunksize value (previous behavior)
"fetch": {
"backtrack_minutes": 100,
},
'verify': {
'chunk_minutes': 10000,
},
},
)
print(f"{simple_pipe.get_chunk_interval()=}")
# simple_pipe.get_chunk_interval()=10000
print(f"{simple_pipe.get_backtrack_interval()=}")
# simple_pipe.get_backtrack_interval()=100
-
Add
flush pipesandflush indices. For convenience, the actionflushis equivalent todrop + syncwithout arguments andclear + syncwith filters (--begin,--end,--params). -
Fix parametrized sync times. Passing
paramstoget_sync_time()correctly returns the appropriate sync time value for multiplexed pipes.
v3.2.3¶
-
Fix environment isolation for
composeprojects. Internal configuration isolation has been reworked to better handle dynamic environments (i.e.composeprojects). -
Prompt user to restart stack when upgrading. When running
mrsm upgrade mrsm, the prompt now asks whether to bring up the stack with the new Docker images after pulling, allowing users to detach from a session when upgrading. -
Add schema to target table on pipe card. The web dashboard's pipe card now shows the fully qualified table name for SQL pipes, including its schema and any truncation.
-
Sort by descending for query data results. The "Query Data" option the web dashboard pipe card now sorts results in descending order.
-
Fix primary key issues with SQLite. Failed primary key creation on SQLite has been fixed.
v3.2.1 โ v3.2.2¶
-
Improve webterm performance. The webterm loads more quickly due to changes made to accomodate xterm.js v6.0.0.
-
Allow for multiple reference pipes. Pipes may inherit parameters from multiple pipes via
references(which now behaves the same asparentsandchildren):
import meerschaum as mrsm
import json
base_pipe_1 = mrsm.Pipe(
'demo', 'base', '1',
instance='sql:local',
dtypes={'a': 'int', 'b': 'int'},
)
base_pipe_2 = mrsm.Pipe(
'demo', 'base', '2',
instance='sql:local',
dtypes={'b': 'bool'},
columns={'primary': 'id'},
)
pipe = mrsm.Pipe(
'demo', 'inherit',
instance='sql:local',
references=[
base_pipe_1,
base_pipe_2,
],
)
print(json.dumps(pipe.get_parameters(), indent=4))
# {
# "dtypes": {
# "a": "int",
# "b": "bool"
# },
# "columns": {
# "primary": "id"
# },
# "references": [
# {
# "connector_keys": "demo",
# "metric_key": "base",
# "location_key": "1",
# "instance_keys": "sql:local"
# },
# {
# "connector_keys": "demo",
# "metric_key": "base",
# "location_key": "2",
# "instance_keys": "sql:local"
# }
# ]
# }
- Add "Resolve symlinks" toggle to pipe parameters editor.
The parameters editor on the web dashboard includes a "Resolve symlinks" switch, which corresponds to the
apply_symlinksparameter forPipe.get_parameters().
v3.2.0¶
- Allow for strings for
reference,parents, andchildren. In addition to keys, reference other pipes with strings of the Pipe constructor:
- Default to the pipe's instance keys for reference pipes.
When referencing other pipes in parameters (
reference,parents,children, etc.), the base pipe's instance keys will be used as the default keys (overriding the environment) to preserve behavior across environments.
### Default instance is 'sql:main'
pipe = mrsm.Pipe('foo', 'bar', columns={'datetime': 'ts'}, instance='sql:local')
downstream_pipe = mrsm.Pipe(
'foo', 'bar', 'baz',
instance='sql:local',
parameters={
'reference': "Pipe('foo', 'bar')",
},
)
print(downstream_pipe.columns)
# {'datetime': 'ts'}
- Add
reference,parents/parent, andchildren/childtoPipeconstructor. For convenience, reference pipes or keys are now able to passed directly into thePipeconstructor. The accepted values may either be dictionaries, strings, or other pipes (or a list of each forparents/children).
pipe = mrsm.Pipe('foo', 'bar', instance='sql:local')
child_pipe = mrsm.Pipe('foo', 'bar', 'baz', parent=pipe, instance='sql:memory')
print(child_pipe.parent)
# Pipe('foo', 'bar', instance='sql:local')
pipe.child = child_pipe
print(pipe.child)
# Pipe('foo', 'bar', 'baz', instance='sql:memory')
- Reference
parentpipes when syncing SQL pipes. If a SQL pipe includes aparentreference, then the date bounding logic injected into the SQL query definition will be more efficiently applied the parent table. This is especially useful for converting between integer and datetime dtypes via SQL.
import meerschaum as mrsm
base_pipe = mrsm.Pipe(
'demo', 'reference', 'parent',
instance='sql:local',
columns={'datetime': 'ts',},
dtypes={'ts': 'int'},
precision='s',
)
base_pipe.sync([{'ts': 1775063535, 'color': 'red'}])
pipe = mrsm.Pipe(
'sql:local', 'reference', 'child',
instance='sql:local',
columns={'datetime': 'ts'},
dtypes={'ts': 'datetime'},
parent=base_pipe, # Reference the parent pipe
parameters={
'sql': (
"SELECT datetime(ts, 'unixepoch') AS ts, color\n"
"FROM {{ Pipe('demo', 'reference', 'parent') }}"
),
},
)
metadef = pipe.instance_connector.get_pipe_metadef(pipe, begin=base_pipe.get_sync_time())
print(metadef)
# WITH "_mrsm_pushdown" AS (
# SELECT *
# FROM "demo_reference_parent"
# WHERE
# "ts" >= 1775063535
# ),
# "definition" AS (
# SELECT datetime(ts, 'unixepoch') AS ts, color
# FROM "_mrsm_pushdown"
# )
# SELECT *
# FROM "definition"
pipe.sync()
base_pipe.sync([{'ts': 1775063837, 'color': 'blue'}])
pipe.sync()
df = pipe.get_data()
print(df)
# ts color
# 0 2026-04-01 17:12:15+00:00 red
# 1 2026-04-01 17:17:17+00:00 blue
-
Allow for changing
datetimedtypes in SQL pipes. When syncing SQL pipes with aparentpipe, the parameters for the parent are considered when building the metadefinition, allowing for changing from integer to timestamp dtypes. -
Add run-time information to pipeline steps. When chaining actions into pipelines, the output steps show the run-time for each step.
-
Improve config patch performance. The config patching logic has been refactored to improve performance.
-
Improve
uvand virtual environment support. Meerschaum now installs correctly withuv tool installand enablesuvwhen run from a virtual environment. -
Fix
deduplicate pipesfor custom schemata. -
Fixed a bug with the
sql:localCLI. -
Fix pickling issue when editing jobs via the Shell.
3.1.x Releases¶
Read release notes
v3.1.8¶
-
Fix issue with CTEs (
WITHkeyword) andUNION ALLsubqueries. A bug has been fixed where queries containing both CTEs (WITH) andUNION ALLqueries would previously break the query parser. -
Add the schema to the
drop pipesconfirmation prompt. To enhance clarification, a SQL pipe's table schema will be included the confirmation prompt. -
Upgrade
xterm.js. The latestxterm.js(v6.0.0) is now included for the webterm. This also includes refactoring work to address scrollbar issues on Chromium-based browsers. -
Upgrade
dashto 4.0. This release upgrades to web dashboard to Plotly Dash 4, which improves the multi-select dropdowns.
v3.1.7¶
-
Fix
is_systemd_available()for older versions ofsystemd. Older versions ofsystemd(e.g. on the Raspberry Pi) are now correctly detected. -
Handle explicitly
Noneenvironments forrun_python_package(). This resolves a subprocess issue withcompose. -
Fix default database configuration for Grafana. New deployments correctly configure the default Meerschaum
-
Properly cancel jobs when CTRL+C is pressed on confirmation.
v3.1.4 โ v3.1.6¶
- Add support for ESRI CRS for
geometrycolumns. In addition to EPSG projections, pipes with geometry data may now sync shapefiles with ESRI (or other authorities, default EPSG) projections.
import meerschaum as mrsm
pipe = mrsm.Pipe(
'demo', 'geometry', 'esri',
columns={'primary': 'id'},
dtypes={'geo': 'geometry[esri:102003]'}
)
pipe.sync([{'id': 1, 'geo': 'POINT (0 0)'}])
gdf = pipe.get_data()
print(gdf.crs.to_authority())
# ('ESRI', '102003')
- Add
Pipe.metricandPipe.locationaliases. LikePipe.connector_keys, the keysmetric_keyandlocation_keymay now be accessed viametricandlocation:
import meerschaum as mrsm
pipe = mrsm.Pipe('demo', 'aliases', target='{{ self.metric }}')
print(pipe.target)
# aliases
- Remove
Nonereturn forPipe.connectorwith invalid keys. Rather than returningNonefor pipes with invalid connectors, return the verbatim connector keys string instead.
import meerschaum as mrsm
pipe = mrsm.Pipe('demo', 'keys')
print(f"{pipe.connector=}")
# pipe.connector='demo'
-
Fix
api_keylogin schemes forapiconnectors. An issue withapiconnectors using theapi_keylogin scheme has been fixed. -
Fix client credentials for remote jobs. When executing a remote action or job using an
apiconnector with client credentials (client_idandclient_secret), scopes are now correctly evaluated.
v3.1.3¶
-
Fix syncing pipes with integer datetimes. An issue with dtype detection has been fixed, improving performance for pipes with integer datetimes.
-
Fix recursive parameters references. A single pipe may now reference itself when symlinking parameters.
-
Add
selfsyntax for parameters symlinking. Pipes may now useselfin parameters rather than requiring the fully qualified name:
import meerschaum as mrsm
pipe = mrsm.Pipe(
'sql:memory', 'symlink', 'self',
instance='sql:memory',
parameters={
"custom": {
"value": 123,
},
"sql": "SELECT * FROM foo WHERE val = {{ self.parameters['custom']['value'] }}",
},
)
print(pipe.parameters['sql'])
# SELECT * FROM foo WHERE val = 123
v3.1.2¶
-
Improve filtering logic for non-datetime pipes. The
Pipe.fiter_existing()method has been fixed to correctly apply non-datetime filters when syncing dataframes. -
Handle mixed nulls when filtering dataframes. Dataframes with different nulls (e.g. empty strings vs
Nonevspd.NA, etc.) are now correctly coerced to reduce excessive updates. -
Fix minor cache race condition. When syncing pipes with many concurrent chunks, additional safeguards have been added to ensure cached values are ready correctly.
-
Bump Docker base to Python 3.14. The Docker image has been rebased to
fedora:43which ships with Python 3.14.
v3.1.0 โ v3.1.1¶
-
Improve
geometryperformance. Enforcing dtypes for pipes withgeometrycolumns has been significantly improved. -
Add safeguards to in-place syncs. Attempting to sync an in-place SQL pipe without index columns configured will now gracefully fail.
-
Add action
delete cache. Users may manually purge local or Valkey cache withdelete cacheorclear cache. -
Clean up stray cache directories. Deleting pipes automatically removes empty cache directories.
-
Deprecate the
apiDocker tag. ThelatestDocker tag now representsfull. Theapitag only exists to retain backwards compatability. -
Drop
citusfrom the test suite. Likecockroachdb, thecitusflavor has been deprecated; please use thepostgresqlflavor instead.
3.0.x Releases¶
Read release notes
v3.0.9 โ v3.0.10¶
-
Disable CLI daemon by default. New installations will now disable the CLI daemon, which may be enabled by setting
system.experimental.cli_daemontotrue. -
Support
geopackageSQLConnectors. The flavorgeopackage(extension ofsqlite) enables syncing geometry data as GeoPackage-WKB (as well as the necessarygpkgtables if missing). -
Check for missing plugins paths for
bootstrap plugin. Thebootstrap pluginwizard now ensures that configured plugins paths (i.e.MRSM_PLUGINS_DIR) exist before proceeding. -
Add FontAwesome icons to the Dash app. Plugins using the
@web_pagefeature will now have access to FontAwesome icons, which are now statically hosted. -
Fix the Valkey stack data directory. The move from
bitnami/valkeytovalkey/valkeyincorrectly pointed the Valkey volume to/valkey/datainstead of/data. This error has been rectified.
v3.0.6 โ v3.0.8¶
-
Fix stack config for new deployments. New stack deployments behave as expected when symlinks fail to resolve.
-
Handle other null-like values when syncing to PostgreSQL. The null detection has been improved when syncing in bulk to PostgreSQL.
-
Fix web dashboard SQL editor. The SQL editor on the web dashboard now correctly updates SQL queries.
-
Add "Resolve symlinks" to web dashboard SQL Editor. The SQL Editor on the pipes cards now includes a toggle to resolve symlinks in the SQL query, allowing you to make changes to the query without overwriting the symlinks.
-
Improve performance when fetching pipes' tags on the web dashboard. Cache misses are now better handled when fetching tags and other keys on the web dashboard.
-
Preserve symlinks when copying pipes. The action
copy pipesnow preserves symlinks for new pipes. -
Fix edge case for
unload_plugins(). When an empty list is explicitly provided, the behavior now does not unload all plugins.
v3.0.3 โ v3.0.5¶
-
Fix pipe page routing. Links to specific pipes in the dashboard are now routed correctly.
-
Fix cache invalidation for pipes using Valkey connectors. Pipes using a
valkeyconnector to store cache (rather than on-disk) will now correctly invalidate cache on demand. -
Fix Python 3.9 compatability. An issue breaking Python 3.9 has been fixed.
-
Prevent resolving symlinks for pipe's parameters card. The pipe card in dash will no longer resolve symlinks.
-
Handle
pd.NAvalues in JSON columns forfilter_unseen_df(). Non-string values (e.g.pd.NA) are now correctly handled for JSON columns infilter_unseen_df(). -
Resolve dependencies when running the
sqlCLI. Running withsqlaction will now resolve expected dependencies. -
Refine unloading plugins. Rather than always popping the root
pluginspackage fromsys.modules, unloaded plugins are instead deleted directly from the root package. The rootpluginspackage is popped only if all plugins are unloaded at once. -
Disable
uvwhen running inside a virtual environment.
v3.0.1 โ v3.0.2¶
-
Change the working directory for each action exected by the CLI daemon. The CLI daemon now changes working directory to match the context of the calling client. This handles relative file paths in environment variables (e.g.
MRSM_PLUGINS_DIR). -
Fix environment variables handling within the CLI daemon. Certain environment variables interfered with the shell and the Daemon, and this case has been handled.
-
Reload the CLI daemon after upgrading packages. Installing or upgrading packages now reloads the CLI daemon.
-
Invalidate symlinks check cache when unloading plugins. Unloading plugins now reverts the internal
_synced_symlinkscheck. -
Unload the root
pluginspackage when unloading plugins. The rootpluginspackage is now unloaded whenunload_plugins()is called. This invalidates lingering cache from previously loaded plugins.
v3.0.0¶
- Inherit another pipe's base parameters with
reference. Pipes may inherit the base parameters of other pipes by setting the keyreference:
import meerschaum as mrsm
base_pipe = mrsm.Pipe(
'demo', 'reference', 'parent',
instance='sql:memory',
columns={
'datetime': 'ts',
'id': 'id',
},
parameters={
'custom': {
'foo': 'bar',
'color': 'red',
},
},
)
base_pipe.register()
pipe = mrsm.Pipe(
'demo', 'reference', 'child',
instance='sql:memory',
parameters={
'reference': {
'connector': 'demo',
'metric': 'reference',
'location': 'parent',
'instance': 'sql:memory',
},
'custom': {
'color': 'blue',
},
},
)
print(pipe.parameters)
# {'custom': {'foo': 'bar', 'color': 'blue'}, 'columns': {'datetime': 'ts', 'id': 'id'}}
- Dynamically symlink to other pipes' attributes.
Reference attributes of other pipes using the
{{ Pipe(...) }}syntax. These references are resolved at run-time whenPipe.parametersis accessed:
import json
import meerschaum as mrsm
some_pipe = mrsm.Pipe(
'demo', 'symlink',
instance='sql:memory',
parameters={
'target': 'some_table',
'columns': {
'datetime': 'id',
'id': 'id',
},
'upsert': True,
'custom': {
'stations': ['KATL', 'KGMU', 'KCEU'],
},
},
)
some_pipe.register()
pipe = mrsm.Pipe(
'demo', 'symlink', 'child',
instance='sql:memory',
parameters={
'target': "{{ Pipe('demo', 'symlink', instance='sql:memory').target }}",
'columns': "{{ Pipe('demo', 'symlink', instance='sql:memory').columns }}",
'upsert': "{{ Pipe('demo', 'symlink', instance='sql:memory').upsert }}",
'custom': "{{ Pipe('demo', 'symlink', instance='sql:memory').parameters['custom'] }}",
'parent_pipe_id': "{{ Pipe('demo', 'symlink', instance='sql:memory').id }}",
},
)
print(json.dumps(pipe.parameters, indent=4))
# {
# "target": "some_table",
# "columns": {
# "datetime": "id",
# "id": "id"
# },
# "upsert": true,
# "custom": {
# "stations": [
# "KATL",
# "KGMU",
# "KCEU"
# ]
# },
# "parent_pipe_id": 1
# }
- Add
MRSMconfig symlinks to pipe parameters. Similar to the{{ Pipe(...) }}syntax, you can now reference your Meerschaum configuration from within a pipe's parameters:
import meerschaum as mrsm
pipe = mrsm.Pipe(
'demo', 'config_symlinks',
parameters = {
'username': 'MRSM{meerschaum:connectors:sql:main:username}',
'password': 'MRSM{meerschaum:connectors:sql:main:password}',
}
)
print(pipe.parameters['username'])
# 'user'
-
Add
Pipe.update_parameters(). Due to the symlinking features, the methodPipe.update_parameters()will now appropriately handle updating the parameters within the pipe's attributes. Therefore, mutatingPipe.parametersno longer affects the state of the pipe directly. -
Add
InstanceConnectorbase class. Custom connectors which implement the instance connectors interface should now inherit fromInstanceConnectoras the base class:
# example.py
from meerschaum.connectors import InstanceConnector, make_connector
@make_connector
class ExampleConnector(InstanceConnector):
def register_pipe(self, pipe: mrsm.Pipe, **kwargs):
...
def get_pipe_attributes(self, pipe: mrsm.Pipe, **kwargs):
...
...
- Add long-lived authentication tokens.
You may now register tokens to programmatically authenticate your applications to a Meerschaum API instance. This is ideal for use cases such as CI/CD, IoT, and other automated workloads. Tokens are restricted by scopes, may expire or be invalidated, and are owned by a user account. Tokens may be managed via the CLI or web console (at
/dash/tokens, underSettings>Tokens).
-
Add scopes to user accounts. Similar to tokens, users may be restricted by scopes. Run
edit userto edit thescopesattribute for a given user (more comprehensive editing to come). -
Set
coerce_typestoTrueinquery_df()if any exclude parameters are provided. Prefacing a value with the negation prefix inparamsforquery_df()will now forcecoerce_typesto beTrue.
import pandas as pd
from meerschaum.utils.dataframe import query_df
df = pd.DataFrame([
{'id': 1, 'color': 'red', 'count': 3},
{'id': 2, 'color': 'blue', 'count': 2},
{'id': 3, 'color': 'red', 'count': 1},
{'id': 4, 'color': 'green', 'count': 3},
])
result_df = query_df(df, {'count': '_3'})
print(result_df)
# id color count
# 1 2 blue 2
# 2 3 red 1
- Project geometry data to WGS84 (EPSG:4326) when serializing as GeoJSON.
Setting
geometry_formattogeojsonforto_json()(default) (andserialize_geometry(), though not default) will project to WGS84 if a CRS is provided (to meet the 2016 GeoJSON specification).
import meerschaum as mrsm
from meerschaum.utils.dataframe import to_json
pipe = mrsm.Pipe(
'demo', 'geojson',
instance='sql:memory',
temporary=True,
columns={'primary': 'id'},
dtypes={
'geometry': 'geometry[point, 6570]',
},
)
pipe.sync([{
'id': 1,
'geometry': 'POINT (1583385.033568 1111981.990433)',
}])
df = pipe.get_data()
json_data = to_json(df)
print(json_data)
# [{"id":1,"geometry":{"type":"Point","coordinates":[-82.389048539797542,34.881760860334396]}}]
-
Upgrade
sql:mainto PostgreSQL 17. The Meerschaum stack database now ships astimescale/timescaledb-ha:pg17, which includes additional extensions such as PostGIS. The flavor forsql:mainis nowtimescaledb-ha(see below). -
Add the SQL flavor
timescaledb-ha. The default instance connectorsql:mainnow has the flavortimescaledb-ha, corresponding to thetimescale/timescaledb-haDocker image. This image includes PostGIS,timescaledb_toolkit, andpg_stat_statements. -
Add support for sets and Series in
query_df(). Sets and Pandas Series withinparamswill now be treated as lists. -
Allow for spaces and an optional
mrsm.prefix for templated SQL query definitions. The template format{{Pipe(...)}}will now match leading and trailing spaces around thePipedeclaration, and an optionalmrsm.prefix is accepted. -
Add
Pipe.autotime. Similar toPipe.autoincrement, settingautotimewill capture the current timestamp for the value of thedatetimeaxis (as datetimes or integers, depending on the dtype):
import time
import meerschaum as mrsm
pipe = mrsm.Pipe(
'demo', 'autotime',
instance='sql:local',
autotime=True,
precision='ms',
temporary=True,
columns={
'datetime': 'ts',
'id': 'id',
},
dtypes={
'ts': 'datetime64[ms, UTC]',
'id': 'int',
},
)
pipe.sync([
{'id': 1, 'val': 100.1},
{'id': 2, 'val': 200.2},
])
time.sleep(3)
pipe.sync([
{'id': 1, 'val': 300.3},
{'id': 2, 'val': 400.4},
])
df = pipe.get_data()
print(df)
# id val ts
# 0 1 100.1 2025-07-22 00:59:19.914000+00:00
# 1 2 200.2 2025-07-22 00:59:19.914000+00:00
# 2 1 300.3 2025-07-22 00:59:23.674000+00:00
# 3 2 400.4 2025-07-22 00:59:23.674000+00:00
- Add
Pipe.precision. The parameterprecisiondetermines the precision (and therefore the size) of the timestamp captured byautotime. Accepted values arenanosecond,microsecond,millisecond,second,minute,hour, andday. The default value ofprecisionis derived from the dtype of thedatetimeaxis (i.e.datetime64[ns]isnanosecondprecision).
import meerschaum as mrsm
day_pipe = mrsm.Pipe(
'demo', 'precision', 'day',
instance='sql:memory',
columns={'datetime': 'day'},
dtypes={'day': 'date'},
autotime=True,
precision=True,
)
day_pipe.sync("val:1")
df = day_pipe.get_data()
print(df)
# val day
# 0 1 2025-07-23
The parameter precision may either be a string (the unit) or a dictionary with the following keys:
unit(required) The precision unit (microsecond,second, etc.)interval(default 1) Optionally round to a specific number of units. For example,precision='minute'andinterval=15would round to 15-minute intervals.-
round_toTo which direction to round the current timestamp. Supported values aredown(default),up, andclosest. Seemeerschaum.utils.dtypes.round_time(). -
Add
Pipe.get_value(). The convenience functionPipe.get_value()selects single values from result sets ofPipe.get_data():
import meerschaum as mrsm
pipe = mrsm.Pipe(
'demo', 'value',
instance='sql:memory',
temporary=True,
autoincrement=True,
columns={'primary': 'id'},
)
pipe.sync([
{'species': 'parrot', 'name': 'Polly'},
{'species': 'dog', 'name': 'Spot'},
{'species': 'rabbit', 'name': 'Peter Cottontail'},
])
spot_id = pipe.get_value('id', params={'name': 'Spot'})
print(spot_id)
# 2
- Add
Pipe.get_doc(). Similar toPipe.get_value(), the methodPipe.get_doc()will return a single row as a dictionary:
import meerschaum as mrsm
pipe = mrsm.Pipe(
'demo', 'doc',
instance='sql:memory',
columns={'primary': 'id'},
)
pipe.sync([
{'id': 1, 'name': 'Alice', 'fav_color': 'red'},
{'id': 2, 'name': 'Bob', 'fav_color': 'green'},
{'id': 3, 'name': 'Charlie', 'fav_color': 'blue'},
])
doc = pipe.get_doc(params={'id': 2})
print(doc)
# {'id': 2, 'name': 'Bob', 'fav_color': 'green'}
-
Add the parameter
mixed_numerics. Settingmixed_numericstoFalsewill prevent the behavior of coercing integer to float columns asnumeric, akin tostatic=Truebut just for this behavior. -
Introducing the Meerschaum CLI daemon. Actions are now routed through a long-lived daemon process, simplifying the number of active connections and cutting latency for CLI commands. The shell includes the command
daemonwhich temporarily toggles between the CLI daemon and executing in-process. Adding the flag--no-daemonto any action will disable the CLI daemon routing, and the CLI daemon may be disabled by settingsystem.experimental.cli_daemontofalse. You may selectively allow or restrict actions prefixes under the configurationsystem.cli.allowed_prefixes(default*) andsystem.cli.disallowed_prefixes. -
Performance improvements through smarter caching. The metadata caching system has been overhauled, drastically reducing redundant work and increasing performance. Pipes' metadata are cached on-disk, and providing
cache_connector_keysto the Pipe constructor will cache to a Valkey instance instead. Setcache=Falseto disable this behavior. -
Restrict Webterms to API processes. In previous releases, a single webterm process was shared amongst API processes. Now each API process requires its own Webterm server, and
tmuxsessions are separated by port. -
Fix custom actions with spaces in Web Console.
-
Ignore
schemafrom pipes' parameters on SQLite. -
Tweak
beginandendinput sizes in the pipes card.