DocsDocumentation

Nitro DataStore

Schema-free JSON data store with dot notation access.

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

IssueSolution
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 mutateAssign the result: data = data.transform_keys(...)

Examples

See the examples/ directory for comprehensive demos:

  • 01_basic_operations.py - Access patterns and CRUD
  • 02_file_operations.py - Load, save, directory merging
  • 03_querying.py - Query builder usage
  • 04_path_operations.py - Path discovery and patterns
  • 05_bulk_operations.py - Batch updates and cleanup
  • 06_data_introspection.py - describe(), stats(), flatten()
  • 07_comparison.py - diff() and equals()
  • 08_comprehensive_example.py - Real-world workflow
  • 09_security_features.py - Security protections

Ecosystem

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.

29 methods
delete(self, key: str) -> bool

Delete a value using dot notation path.

describe(self) -> Dict[str, Any]

Get a structural description of the data.

diff(self, other: Union[ForwardRef('NitroDataStore'), Dict[str, Any]]) -> Dict[str, Any]

Compare this datastore with another and return differences.

equals(self, other: Union[ForwardRef('NitroDataStore'), Dict[str, Any]]) -> bool

Check if this datastore is equal to another.

filter_list(self, path: str, predicate: Callable[[Any], bool]) -> List[Any]

Filter a list at the given path.

find_all_keys(self, key_name: str) -> Dict[str, Any]

Find all occurrences of a key name anywhere in the structure.

find_paths(self, pattern: str, separator: str = '.') -> List[str]

Find paths matching a glob-like pattern.

find_values(self, predicate: Callable[[Any], bool]) -> Dict[str, Any]

Find all values matching a predicate function.

flatten(self, separator: str = '.') -> Dict[str, Any]

Flatten nested dictionary to dot-notation keys.

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'

Load and merge all JSON files from a directory.

from_file(file_path: Union[str, pathlib.Path], base_dir: Union[str, pathlib.Path, NoneType] = None, max_size: Optional[int] = None) -> 'NitroDataStore'

Load data from a JSON file.

get(self, key: str, default: Any = None) -> Any

Get a value using dot notation path.

get_many(self, paths: List[str]) -> Dict[str, Any]

Get multiple values by their paths.

has(self, key: str) -> bool

Check if a key exists using dot notation path.

items(self) -> Iterator[tuple]

Get top-level key-value pairs.

keys(self) -> Iterator[str]

Get top-level keys.

list_paths(self, prefix: str = '', separator: str = '.') -> List[str]

List all paths in the data structure.

merge(self, other: Union[ForwardRef('NitroDataStore'), Dict[str, Any]]) -> None

Deep merge another data store or dictionary into this one.

query(self, path: str) -> 'QueryBuilder'

Start a query builder for filtering and transforming data.

remove_empty(self) -> int

Remove all empty dicts and lists from the data structure.

remove_nulls(self) -> int

Remove all None values from the data structure.

save(self, file_path: Union[str, pathlib.Path], indent: int = 2) -> None

Save data to a JSON file.

set(self, key: str, value: Any) -> None

Set a value using dot notation path.

stats(self) -> Dict[str, int]

Get statistics about the data structure.

to_dict(self) -> Dict[str, Any]

Export data as a plain dictionary.

transform_all(self, transform: Callable[[str, Any], Any]) -> 'NitroDataStore'

Create a new datastore with all values transformed.

transform_keys(self, transform: Callable[[str], str]) -> 'NitroDataStore'

Create a new datastore with all keys transformed.

update_where(self, condition: Callable[[str, Any], bool], transform: Callable[[Any], Any]) -> int

Update all values matching a condition.

values(self) -> Iterator[Any]

Get top-level values.

QueryBuilder

QueryBuilder#

QueryBuilder(collection: List[Any])

Chainable query builder for filtering, sorting, and limiting collections.

9 methods
count(self) -> int

Count results without executing full query.

execute(self) -> List[Any]

Execute the query and return results.

first(self) -> Optional[Any]

Get the first result.

group_by(self, key: str) -> Dict[Any, List[Any]]

Group results by a field value.

limit(self, count: int) -> 'QueryBuilder'

Limit the number of results.

offset(self, count: int) -> 'QueryBuilder'

Skip a number of results.

pluck(self, key: str) -> List[Any]

Extract a single field from all results.

sort(self, key: Optional[Callable[[Any], Any]] = None, reverse: bool = False) -> 'QueryBuilder'

Sort the results.

where(self, predicate: Callable[[Any], bool]) -> 'QueryBuilder'

Add a filter condition.