A flexible, schema-free data store for JSON in Python. Access nested data with dot notation, dictionary style, or path strings.
from nitro_datastore import NitroDataStore
data = NitroDataStore({'site': {'name': 'Nitro', 'url': 'https://nitro.sh'}})
data.site.name # Dot notation
data['site']['name'] # Dictionary style
data.get('site.name') # Path-based with defaults
No more config.get('site', {}).get('theme', {}).get('color', '#000'). Just config.get('site.theme.color', '#000').
Installation
pip install nitro-datastore
For AI coding assistants (Claude Code, etc.):
npx skills add nitrosh/nitro-datastore
Quick Start
Creating a DataStore
# From a dictionary
data = NitroDataStore({'title': 'Hello', 'settings': {'theme': 'dark'}})
# From a JSON file
data = NitroDataStore.from_file('config.json')
# From a directory (auto-merges all JSON files alphabetically)
data = NitroDataStore.from_directory('data/')
Reading & Writing
# Get with defaults
name = data.get('user.name', 'Anonymous')
# Set (creates intermediate dicts automatically)
data.set('config.cache.ttl', 3600)
# Check existence
if data.has('user.email'):
email = data.get('user.email')
# Delete
data.delete('user.temp_token')
# Merge another datastore
data.merge(other_data)
Saving
data.save('output.json', indent=2)
plain_dict = data.to_dict()
Query Builder
Filter and transform collections with a chainable API:
data = NitroDataStore({
'posts': [
{'title': 'Python Tips', 'views': 150, 'published': True},
{'title': 'Web Dev', 'views': 200, 'published': True},
{'title': 'Draft', 'views': 0, 'published': False}
]
})
# Filter, sort, limit
results = (data.query('posts')
.where(lambda p: p.get('published'))
.sort(key=lambda p: p.get('views'), reverse=True)
.limit(10)
.execute())
# Utilities
count = data.query('posts').where(lambda p: p.get('published')).count()
titles = data.query('posts').pluck('title')
by_category = data.query('posts').group_by('category')
first = data.query('posts').first()
Path Discovery
Explore unknown data structures:
# List all paths
paths = data.list_paths()
# Glob patterns (* = single segment, ** = any depth)
titles = data.find_paths('posts.*.title')
urls = data.find_paths('**.url')
# Find all occurrences of a key
all_urls = data.find_all_keys('url')
# Find values by predicate
emails = data.find_values(lambda v: isinstance(v, str) and '@' in v)
Bulk Operations
# Update all matching values
count = data.update_where(
condition=lambda path, value: 'http://' in str(value),
transform=lambda value: value.replace('http://', 'https://')
)
# Clean up
data.remove_nulls()
data.remove_empty()
Transformations
Transformations return new instances (immutable):
# Transform all values
upper = data.transform_all(lambda path, v: v.upper() if isinstance(v, str) else v)
# Transform all keys (e.g., kebab-case to snake_case)
snake = data.transform_keys(lambda k: k.replace('-', '_'))
Comparison
data1.equals(data2) # True/False
diff = old.diff(new)
# {'added': {...}, 'removed': {...}, 'changed': {...}}
Security
Built-in protections for file operations:
# Path traversal protection
data = NitroDataStore.from_file(user_path, base_dir='/safe/directory')
# File size limits
data = NitroDataStore.from_file(path, max_size=10*1024*1024)
Also includes: path validation, circular reference detection.
Common Gotchas
| Issue | Solution |
|---|---|
Kebab-case keys (user-name) | Use data['user-name'] instead of dot notation |
Keys with dots (key.name) | Use data['key.name'] - path methods treat dots as separators |
| Transform doesn't mutate | Assign the result: data = data.transform_keys(...) |
Examples
See the examples/ directory for comprehensive demos:
01_basic_operations.py- Access patterns and CRUD02_file_operations.py- Load, save, directory merging03_querying.py- Query builder usage04_path_operations.py- Path discovery and patterns05_bulk_operations.py- Batch updates and cleanup06_data_introspection.py- describe(), stats(), flatten()07_comparison.py- diff() and equals()08_comprehensive_example.py- Real-world workflow09_security_features.py- Security protections
Ecosystem
- nitro-ui - Build HTML with Python, not strings
- nitro-cli - Static site generator powered by nitro-ui
- nitro-dispatch - Framework-agnostic plugin system
- nitro-image - Fast, friendly image processing for the web
- nitro-validate - Dependency-free data validation
License
MIT License. See LICENSE for details.
API Reference
Auto-generated from the installed package's public API. Signatures and docstrings come directly from the source.
NitroDataStore
NitroDataStore#
NitroDataStore(data: Optional[Dict[str, Any]] = None)A flexible data store for accessing and manipulating JSON data. Delete a value using dot notation path. Get a structural description of the data. Compare this datastore with another and return differences. Check if this datastore is equal to another. Filter a list at the given path. Find all occurrences of a key name anywhere in the structure. Find paths matching a glob-like pattern. Find all values matching a predicate function. Flatten nested dictionary to dot-notation keys. Load and merge all JSON files from a directory. Load data from a JSON file. Get a value using dot notation path. Get multiple values by their paths. Check if a key exists using dot notation path. Get top-level key-value pairs. Get top-level keys. List all paths in the data structure. Deep merge another data store or dictionary into this one. Start a query builder for filtering and transforming data. Remove all empty dicts and lists from the data structure. Remove all None values from the data structure. Save data to a JSON file. Set a value using dot notation path. Get statistics about the data structure. Export data as a plain dictionary. Create a new datastore with all values transformed. Create a new datastore with all keys transformed. Update all values matching a condition. Get top-level values.29 methods
delete(self, key: str) -> booldescribe(self) -> Dict[str, Any]diff(self, other: Union[ForwardRef('NitroDataStore'), Dict[str, Any]]) -> Dict[str, Any]equals(self, other: Union[ForwardRef('NitroDataStore'), Dict[str, Any]]) -> boolfilter_list(self, path: str, predicate: Callable[[Any], bool]) -> List[Any]find_all_keys(self, key_name: str) -> Dict[str, Any]find_paths(self, pattern: str, separator: str = '.') -> List[str]find_values(self, predicate: Callable[[Any], bool]) -> Dict[str, Any]flatten(self, separator: str = '.') -> Dict[str, Any]from_directory(directory: Union[str, pathlib.Path], pattern: str = '*.json', base_dir: Union[str, pathlib.Path, NoneType] = None, max_size: Optional[int] = None, strict: bool = False) -> 'NitroDataStore'from_file(file_path: Union[str, pathlib.Path], base_dir: Union[str, pathlib.Path, NoneType] = None, max_size: Optional[int] = None) -> 'NitroDataStore'get(self, key: str, default: Any = None) -> Anyget_many(self, paths: List[str]) -> Dict[str, Any]has(self, key: str) -> boolitems(self) -> Iterator[tuple]keys(self) -> Iterator[str]list_paths(self, prefix: str = '', separator: str = '.') -> List[str]merge(self, other: Union[ForwardRef('NitroDataStore'), Dict[str, Any]]) -> Nonequery(self, path: str) -> 'QueryBuilder'remove_empty(self) -> intremove_nulls(self) -> intsave(self, file_path: Union[str, pathlib.Path], indent: int = 2) -> Noneset(self, key: str, value: Any) -> Nonestats(self) -> Dict[str, int]to_dict(self) -> Dict[str, Any]transform_all(self, transform: Callable[[str, Any], Any]) -> 'NitroDataStore'transform_keys(self, transform: Callable[[str], str]) -> 'NitroDataStore'update_where(self, condition: Callable[[str, Any], bool], transform: Callable[[Any], Any]) -> intvalues(self) -> Iterator[Any]
QueryBuilder
QueryBuilder#
QueryBuilder(collection: List[Any])Chainable query builder for filtering, sorting, and limiting collections. Count results without executing full query. Execute the query and return results. Get the first result. Group results by a field value. Limit the number of results. Skip a number of results. Extract a single field from all results. Sort the results. Add a filter condition.9 methods
count(self) -> intexecute(self) -> List[Any]first(self) -> Optional[Any]group_by(self, key: str) -> Dict[Any, List[Any]]limit(self, count: int) -> 'QueryBuilder'offset(self, count: int) -> 'QueryBuilder'pluck(self, key: str) -> List[Any]sort(self, key: Optional[Callable[[Any], Any]] = None, reverse: bool = False) -> 'QueryBuilder'where(self, predicate: Callable[[Any], bool]) -> 'QueryBuilder'