Easily manage data storage and logging across repos
Project description
Cabinet
Cabinet is a lightweight, flexible data organization tool that lets you manage your data with the simplicity of a JSON file or the power of MongoDB - your choice.
✨ Features
- Access your data across multiple projects
- Log messages to MongoDB or a file of your choice
- Edit MongoDB as though it were a JSON file
- Send mail from the terminal
- Library for interactive command-line interface components using
prompt_toolkit
Breaking change in 2.0.0
mongodb_connection_stringreplacesmongodb_usernameandmongodb_password.
Installation and Setup
CLI and Python Library (Recommended)
-
Install pipx if you don't have it already
-
Install
cabinet:
pipx install cabinet
cabinet --config
CLI Only
curl -s https://api.github.com/repos/tylerjwoodfin/cabinet/releases/latest \
| grep "browser_download_url" \
| cut -d '"' -f 4 \
| xargs curl -L -o cabinet.pex
sudo mv cabinet.pex /usr/local/bin/cabinet
Dependencies
Outside of the standard Python library, the following packages are included as part of pipx install cabinet:
pymongo: Provides the MongoDB client and related errors.prompt_toolkit: Provides functionality for command-line interfaces.
Structure
- Data is stored in
~/.cabinet/data.jsonor MongoDB- data from MongoDB is interacted with as if it were a JSON file
- cache is written when retrieving data.
- if cache is older than 1 hour, it is refreshed; otherwise, data is pulled from cache by default
- Logs are written to
~/.cabinet/log/LOG_DAILY_YYYY-MM-DDby default- You can change the path to something other than
~/.cabinet/logas needed by setting/modifying~/.config/cabinet/config.json->path_dir_log
- You can change the path to something other than
CLI usage
Usage: cabinet [OPTIONS]
Options:
-h, --help show this help message and exit
--configure, -config Configure
--edit, -e Edit Cabinet as MongoDB as a JSON file
--edit-file EDIT_FILE, -ef EDIT_FILE
Edit a specific file
--force-cache-update Disable using the cache for MongoDB queries
--no-create (for -ef) Do not create file if it does not exist
--get GET [GET ...], -g GET [GET ...]
Get a property from MongoDB
--put PUT [PUT ...], -p PUT [PUT ...]
Put a property into MongoDB
--remove REMOVE [REMOVE ...], -rm REMOVE [REMOVE ...]
Remove a property from MongoDB
--get-file GET_FILE Get file
--export Exports MongoDB to ~/.cabinet/export
--strip (for --get-file) Whether to strip file content whitespace
--log LOG, -l LOG Log a message to the default location
--level LOG_LEVEL (for -l) Log level [debug, info, warn, error, critical]
--tags LOG_TAGS (for -l) Comma-separated list of tags to associate with the log entry
--query [LOG_QUERY_FILE], -q [LOG_QUERY_FILE]
Query log files (optional: specify log file name, defaults to today)
--query-tags QUERY_TAGS
(for --query) Comma-separated list of tags to filter by
--query-path QUERY_PATH
(for --query) Filter by file path (fuzzy search)
--query-hostname QUERY_HOSTNAME
(for --query) Filter by hostname
--query-level QUERY_LEVEL
(for --query) Filter by log level [debug, info, warning, error, critical]
--query-date QUERY_DATE
(for --query) Filter by date (YYYY-MM-DD format)
--query-message QUERY_MESSAGE
(for --query) Search within message text
--editor EDITOR (for --edit and --edit-file) Specify an editor to use
-v, --version Show version number and exit
Mail:
--mail Sends an email
--subject SUBJECT, -s SUBJECT
Email subject
--body BODY, -b BODY Email body
--to TO_ADDR, -t TO_ADDR
The "to" email address
Configuration
-
Configuration data is stored in
~/.config/cabinet/config.json. -
Upon first launch, the tool will walk you through each option.
path_dir_logis the directory where logs will be stored by default.mongodb_enabledis a boolean that determines whether MongoDB is used.mongodb_db_nameis the name of the database you want to use by default.mongodb_connection_stringis the connection string for MongoDB.editoris the default editor that will be used when editing files.- You will be prompted to enter your MongoDB credentials (optional).
- If you choose not to use MongoDB, data will be stored in
~/.cabinet/data.json.
-
Follow these instructions to find your MongoDB connection string: MongoDB Atlas or MongoDB (for local MongoDB, untested).
-
You will be asked to configure your default editor from the list of available editors on your system. If this step is skipped, or an error occurs,
nanowill be used.You can change this with
cabinet --configand modifying theeditorattribute.
Your config.json should look something like this:
{
"path_dir_log": "/path/to/your/log/directory",
"mongodb_db_name": "cabinet (or other name of your choice)",
"editor": "nvim",
"mongodb_enabled": true,
"mongodb_connection_string": "<your connection string>",
}
edit_file() shortcuts
- see example below to enable something like
cabinet -ef shoppingfrom the terminal- rather than
cabinet -ef "~/path/to/shopping_list.md"
- rather than
- or
cabinet.Cabinet().edit("shopping")- rather than
cabinet.Cabinet().edit("~/path/to/whatever.md")
- rather than
file:
# example only; these commands will be unique to your setup
{
"path": {
"edit": {
"shopping": {
"value": "~/path/to/whatever.md",
},
"todo": {
"value": "~/path/to/whatever.md",
}
}
}
}
set from terminal:
cabinet -p edit shopping value "~/path/to/whatever.md"
cabinet -p edit todo value "~/path/to/whatever.md"
- It is NEVER a good idea to store your password openly either locally or in MongoDB; for this reason, I recommend a "throwaway" account that is only used for sending emails, such as a custom domain email.
- Gmail and most other mainstream email providers won't work with this; for support, search for sending mail from your email provider with
smtplib. - In Cabinet (
cabinet -e), add theemailobject to make your settings file look like this example:
file:
{
"email": {
"from": "throwaway@example.com",
"from_pw": "example",
"from_name": "Cabinet (or other name of your choice)",
"to": "destination@protonmail.com",
"smtp_server": "example.com",
"imap_server": "example.com",
"port": 123
}
}
set from terminal:
cabinet -p email from throwaway@example.com
cabinet -p email from_pw example
...
Examples
put
python:
from cabinet import Cabinet
cab = Cabinet()
cab.put("employee", "Tyler", "salary", 7.25)
or terminal:
cabinet -p employee Tyler salary 7.25
results in this structure in MongoDB:
{
"employee": {
"Tyler": {
"salary": 7.25 # or "7.25" if done from terminal
}
}
}
get
python:
from cabinet import Cabinet
cab = Cabinet()
print(cab.get("employee", "Tyler", "salary"))
# or cab.get("employee", "Tyler", "salary", is_print = True)
or terminal:
cabinet -g employee Tyler salary
- optional:
--force-cache-updateto force a cache update
results in:
7.25
remove
python:
from cabinet import Cabinet
cab = Cabinet()
cab.remove("employee", "Tyler", "salary")
or terminal:
cabinet -rm employee Tyler salary
results in this structure in MongoDB:
{
"employee": {
"tyler": {}
}
}
edit
terminal:
# opens file in the default editor (`cabinet --config` -> 'editor'), saves upon exit
cabinet -e
# or
cabinet --edit
# you can add an 'editor':
cabinet -e --editor=code
edit_file
python:
from cabinet import Cabinet
cab = Cabinet()
# if put("path", "edit", "shopping", "/path/to/shopping.md") has been called, this will edit the file assigned to that shortcut.
# opens file in the default editor (`cabinet --config` -> 'editor'), saves upon exit
cab.edit("shopping")
# or you can edit a file directly...
cab.edit("/path/to/shopping.md")
# you can pass an 'editor' to override the default:
cab.edit("/path/to/shopping.md", editor="nvim")
terminal:
# assumes path -> edit -> shopping -> path/to/shopping.md has been set
cabinet -ef shoppping
# or
cabinet -ef "/path/to/shopping.md"
# or
mail
python:
from cabinet import Mail
mail = Mail()
mail.send('Test Subject', 'Test Body')
terminal:
cabinet --mail --subject "Test Subject" --body "Test Body"
# or
cabinet --mail -s "Test Subject" -b "Test Body"
log
python:
from cabinet import Cabinet
cab = Cabinet()
# writes to a file named LOG_DAILY_YYYY-MM-DD in `~/.cabinet/log` inside a YYYY-MM-DD folder
# writes somewhere other than `~/.cabinet/log`, if `~/.config/cabinet/config.json` has `path_dir_log` set
cab.log("Connection timed out") # defaults to 'info' if no level is set
cab.log("This function hit a breakpoint", level="debug")
cab.log("Looks like the server is on fire", level="critical")
cab.log("This is fine", level="info")
# NEW: Log with tags (optional list of strings)
cab.log("Checked weather successfully", tags=["weather"])
cab.log("Starting Borg Backup...", tags=["backup", "start"])
cab.log("Pruning repository", tags=["backup", "prune"])
cab.log("Compacting repository", tags=["backup", "compact"])
# writes to a file named LOG_TEMPERATURE in the default log directory
cab.log("30", log_name="LOG_TEMPERATURE")
# writes to a file named LOG_TEMPERATURE in ~/weather
cab.log("30", log_name="LOG_TEMPERATURE", log_folder_path="~/weather")
# format (without tags)
# 2025-10-28 17:01:01,858 — INFO -> tools/weather.py:34@{hostname} -> Checking weather
# format (with tags)
# 2025-09-27 02:01:09,012 — INFO [weather] -> tools/weather.py:116@cloud -> Checked weather successfully
# 2025-09-27 03:05:03,732 — INFO [backup,start] -> bin/cabinet:8@cloud -> Starting Borg Backup...
terminal:
# defaults to 'info' if no level is set
cabinet -l "Connection timed out"
# -l and --log are interchangeable
cabinet --log "Connection timed out"
# change levels with --level
cabinet --log "Server is on fire" --level "critical"
# add tags with --tags (comma-separated)
cabinet --log "Checked weather successfully" --tags "weather"
cabinet --log "Starting Borg Backup..." --tags "backup,start"
cabinet --log "Pruning repository" --level "info" --tags "backup,prune"
log_query
Query log files by various criteria including tags, path, hostname, level, date, and message content.
python:
from cabinet import Cabinet
cab = Cabinet()
# Query today's log file by tag (log_file is optional and defaults to today)
results = cab.log_query(tags=["weather"])
# Returns: ['2025-10-28 17:01:09,012 — INFO [weather] -> tools/weather.py:116@cloud -> Checked weather successfully']
# Query by multiple tags in today's log (returns logs with any of these tags)
results = cab.log_query(tags=["backup"])
# Returns all logs with 'backup' tag from today
# Query a specific date's log file
results = cab.log_query("LOG_DAILY_2025-09-27.log", tags=["weather"])
# Query by log level (in today's log)
results = cab.log_query(level="ERROR")
# Query by message content (case-insensitive fuzzy search)
results = cab.log_query(message="repository")
# Query by path (case-insensitive fuzzy search on file path after arrow)
results = cab.log_query(path="cabinet")
# Query by hostname
results = cab.log_query(hostname="cloud")
# Query by date (filters by timestamp in the log entry)
results = cab.log_query(date_filter="2025-09-27")
# Combine multiple filters on today's log
results = cab.log_query(
tags=["backup"],
level="INFO",
message="repository"
)
# Combine multiple filters on specific log file
results = cab.log_query(
"LOG_DAILY_2025-09-27.log",
tags=["backup"],
level="INFO",
message="repository"
)
terminal:
# Query today's log (defaults to today if no log file specified)
cabinet --query --query-tags "weather"
# Short form
cabinet -q --query-tags "backup"
# Query by level
cabinet --query --query-level "ERROR"
# Query by message content
cabinet --query --query-message "repository"
# Query by path (fuzzy search)
cabinet --query --query-path "tools"
# Query by hostname
cabinet --query --query-hostname "cloud"
# Query by date
cabinet --query --query-date "2025-10-28"
# Combine multiple filters
cabinet --query --query-tags "backup" --query-level "INFO"
# Query specific log file
cabinet --query "LOG_DAILY_2025-09-27.log" --query-tags "weather"
# Complex query with multiple filters
cabinet -q "LOG_DAILY_2025-09-27.log" --query-tags "backup,weather" --query-level "INFO" --query-message "repository"
logdb
python:
from cabinet import Cabinet
cab = Cabinet()
cab.logdb("Connection timed out") # logs default to a `logs` collection in MongoDB
cab.logdb("This function hit a breakpoint", level="debug", collection_name="debugging logs") # customize the collection name
cab.logdb("Temperature changed significantly", level="critical", db_name="weather") # customize the database name
cab.logdb("This is fine", level="info", cluster_name="myCluster") # customize the cluster name
terminal:
# defaults to 'info' if no level is set
cabinet -ldb "Connection timed out"
# -l and --log are interchangeable
cabinet --logdb "Connection timed out"
# change levels with --level
cabinet --logdb "Server is on fire" --level "critical"
UI Module
The cabinet.ui module provides interactive command-line interface components:
from cabinet.ui import list_selection, render_html, confirmation
# List selection
items = ["Option 1", "Option 2", "Option 3"]
selected_index = list_selection(items, "Choose an option:")
# HTML rendering
render_html("<b>Bold text</b> and <i>italic text</i>")
# Confirmation dialog
result = confirmation("Do you want to proceed?", "Confirmation")
Disclaimers
- Although I've done quite a bit of testing, I can't guarantee everything that works on my machine will work on yours. Always back up your data to multiple places to avoid data loss.
- If you find any issues, please contact me... or get your hands dirty and raise a PR!
Author
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file cabinet-1!2.2.0.tar.gz.
File metadata
- Download URL: cabinet-1!2.2.0.tar.gz
- Upload date:
- Size: 31.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
05901a6914ff1a8bad8ffa40e319011e37c59aef5c6b330ce6e071a0bcd6b408
|
|
| MD5 |
e43ab8a8ca5479de4df0b3aa48594e35
|
|
| BLAKE2b-256 |
904d58ac8a069d98d81329118c047e3dfc1ecf2ad117a4b47a97e94109cdae5f
|
File details
Details for the file cabinet-1!2.2.0-py3-none-any.whl.
File metadata
- Download URL: cabinet-1!2.2.0-py3-none-any.whl
- Upload date:
- Size: 28.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5172d878c4171c5ef8d1b3802a31cbec1a5852618dc707e9f459c78180f6f36e
|
|
| MD5 |
5d3f984cc272c06a1098bccbf26e75e8
|
|
| BLAKE2b-256 |
00d4c6ce75c75aeaa8af01b23c81ed627856204ab7d295cda1d363eb8de3d9f7
|