Name | simplesingletable JSON |
Version |
12.3.0
JSON |
| download |
home_page | None |
Summary | A simple boto3/Pydantic implementation of DynamoDB Single Table Design and related utilities. |
upload_time | 2025-09-16 16:19:24 |
maintainer | None |
docs_url | None |
author | None |
requires_python | >=3.10 |
license | MIT |
keywords |
dynamodb
singletabledesign
|
VCS |
 |
bugtrack_url |
|
requirements |
No requirements were recorded.
|
Travis-CI |
No Travis.
|
coveralls test coverage |
No coveralls.
|
# Simple Single Table
**Latest Version:** 12.3.0
## Project Overview
**simplesingletable** is a Python library providing an abstraction layer for AWS DynamoDB operations, specifically
designed for single-table design patterns. The library uses Pydantic for model definitions and includes "
batteries-included" functionality for common DynamoDB use cases.
### Target Use Cases
- Small to medium scale applications
- Single-table DynamoDB design patterns
- Applications requiring versioned resources with automatic history tracking
- Fast, consistent, and cost-effective storage solutions
## Key Features
1. **Single Table Design**: Store different object types in a single DynamoDB table
2. **Automatic ID Generation**: Uses lexicographically sortable IDs via `ulid-py` for chronological ordering
3. **Resource Versioning**: Automatic versioning with complete history and optimistic concurrency control
4. **Secondary Access Patterns**: Support for GSI-based queries and filtering
5. **Pydantic Integration**: Type-safe models with validation
6. **Compression Support**: Optional gzip compression for large data
## Architecture
### Core Components
#### Models (`src/simplesingletable/models.py`)
- **`BaseDynamoDbResource`**: Abstract base class for all resources
- **`DynamoDbResource`**: Non-versioned resources (simpler, lighter)
- **`DynamoDbVersionedResource`**: Versioned resources with automatic history tracking
- **`PaginatedList`**: Enhanced list for paginated query results
#### Memory Layer (`src/simplesingletable/dynamodb_memory.py`)
- **`DynamoDbMemory`**: Main interface for DynamoDB operations (CRUD, queries, filtering)
#### Utilities (`src/simplesingletable/utils.py`)
- ID generation, pagination helpers, DynamoDB type marshalling
### Resource Types
**Non-Versioned Resources** (`DynamoDbResource`):
- Lighter weight, direct updates
- No version history
- Fields: `resource_id`, `created_at`, `updated_at`
**Versioned Resources** (`DynamoDbVersionedResource`):
- Complete version history
- Optimistic concurrency control
- Version limit enforcement (configurable via `max_versions`)
- Compressed data storage by default
- Fields: `resource_id`, `version`, `created_at`, `updated_at`
### GSI (Global Secondary Index) Configuration
The library supports both static and dynamic GSI configuration:
```python
class MyResource(DynamoDbVersionedResource):
# Static configuration
gsi_config: ClassVar[Dict[str, IndexFieldConfig]] = {
"status": {
"pk": lambda self: f"status#{self.status}",
"sk": lambda self: self.resource_id, # Sort by creation time
}
}
# Or dynamic configuration via get_gsi_config()
@classmethod
def get_gsi_config(cls) -> Dict[str, IndexFieldConfig]:
return {"dynamic_index": {"pk": lambda self: f"type#{self.type}"}}
```
### Extras Module (`src/simplesingletable/extras/`)
Additional patterns and utilities:
- **Repository Pattern**: `ResourceRepository` for higher-level operations
- **Versioned Repository**: `VersionedResourceRepository` with version management
- **Singleton Pattern**: For configuration resources
- **Form Data**: Streamlit integration helpers
- **Habit Tracker**: Example application
## Development Setup
### Dependencies
**Core Dependencies**:
- `boto3` - AWS SDK
- `pydantic>2` - Data validation and serialization
- `ulid-py` - Unique ID generation
- `humanize` - Human-readable formatting
**Development Dependencies**:
- `pytest` + `pytest-cov` + `pytest-docker` - Testing framework
- `black` + `isort` + `ruff` - Code formatting and linting
- `invoke` - Task automation
- `bumpver` - Version management
### Build and Test Commands
The project uses `invoke` for task automation (`tasks.py`):
```bash
# Dependency management
inv compile-requirements # Compile requirements.txt from pyproject.toml
inv compile-requirements --upgrade # Update dependencies
# Development
inv lint # Format and lint code (black, isort, ruff)
inv launch-dynamodb-local # Start local DynamoDB for testing
inv halt-dynamodb-local # Stop local DynamoDB
# Testing
pytest # Run test suite with coverage
pytest tests/test_specific.py # Run specific tests
# Release management
inv bumpver --patch|minor|major # Bump version
inv build # Build distribution packages
inv fullrelease --patch # Complete release cycle (lint, test, bump, build, publish)
```
### Testing Architecture
- **Docker-based**: Uses DynamoDB Local via Docker Compose
- **Pytest fixtures**: `conftest.py` provides database setup/teardown
- **Comprehensive coverage**: Tests for CRUD, versioning, GSI, filtering
- **Test files**:
- `test_simplesingletable.py` - Core functionality
- `test_versioned_repository.py` - Version management
- `test_repository.py` - Repository pattern
- `test_filter_expressions.py` - Query filtering
## Important Patterns and Conventions
### Resource Definition Pattern
```python
from simplesingletable import DynamoDbVersionedResource
class MyResource(DynamoDbVersionedResource):
name: str
status: str
metadata: Optional[dict] = None
# the following are defined on the base class
# resource_id: str
# version: int
# created_at: datetime
# updated_at: datetime
# Optional: Configure compression and version limits
resource_config: ClassVar[ResourceConfig] = ResourceConfig(
compress_data=True, # Default for versioned
max_versions=10 # Keep only 10 versions
)
# Optional: GSI configuration for secondary access patterns
def db_get_gsi1pk(self) -> str | None:
return f"status#{self.status}"
```
### CRUD Operations Pattern
```python
from simplesingletable import DynamoDbMemory
memory = DynamoDbMemory(logger=logger, table_name="my-table")
# Create
resource = memory.create_new(MyResource, {"name": "test", "status": "active"})
# Read
retrieved = memory.read_existing(resource.resource_id, MyResource)
# Update (versioned resources automatically increment version)
updated = memory.update_existing(retrieved, {"status": "inactive"})
# List with filtering
resources = memory.list_resources(
MyResource,
filter_fn=lambda r: r.status == "active",
limit=50
)
```
### Paginated Queries
The library provides powerful paginated query capabilities for efficient data retrieval from DynamoDB, supporting both primary key and GSI-based queries with filtering.
#### Basic Paginated Query
```python
from simplesingletable import exhaust_pagination
# Direct paginated query with GSI
results = memory.paginated_dynamodb_query(
resource_class=MyResource,
index_name="gsi1",
key_condition=Key("gsi1pk").eq("status#active"),
results_limit=100, # Items per page
pagination_key=None # For first page
)
# Get all results (automatically handles pagination)
all_results = []
for page in exhaust_pagination(
lambda pk=None: memory.paginated_dynamodb_query(
resource_class=MyResource,
index_name="gsi1",
key_condition=Key("gsi1pk").eq("status#active"),
results_limit=100,
pagination_key=pk
)
):
all_results.extend(page)
```
#### Query Patterns with GSI
Define query methods on your resource classes for reusable access patterns:
```python
class MyResource(DynamoDbVersionedResource):
status: str
category: str
def db_get_gsi1pk(self) -> str | None:
# Sparse GSI - only index active items
if self.status == "active":
return f"{self.get_unique_key_prefix()}#{self.category}"
return None
@classmethod
def query_by_category_kwargs(cls, category: str):
"""Build query kwargs for category-based queries."""
return {
"index_name": "gsi1",
"key_condition": Key("gsi1pk").eq(f"{cls.get_unique_key_prefix()}#{category}")
}
# Use the query pattern
active_in_category = memory.paginated_dynamodb_query(
resource_class=MyResource,
**MyResource.query_by_category_kwargs("important"),
results_limit=50
)
```
#### Advanced Filtering
**Important Note on Versioned Resources**: Versioned resources use compression by default, which means DynamoDB filter expressions can only access attributes that are part of a GSI (pk, sk, gsi1pk, gsi1sk, etc.). For compressed resources, attribute-based filtering must be done client-side. Non-versioned resources don't have this limitation.
```python
from boto3.dynamodb.conditions import Attr
# For NON-VERSIONED resources - DynamoDB-side filtering works
filter_expression = Attr("status").eq("active") & Attr("priority").gt(5)
# For VERSIONED resources - use client-side filtering or GSI design
def custom_filter(resource: MyVersionedResource) -> bool:
return resource.status == "active" and resource.priority > 5
# Combined approach (versioned resource example)
results = memory.paginated_dynamodb_query(
resource_class=MyVersionedResource,
filter_fn=custom_filter, # Client-side only for versioned
results_limit=100
)
```
**Best Practices for Efficient Filtering**:
1. **Design GSIs carefully** - For versioned resources, encode filterable attributes in GSI keys
2. **Consider data volume** - Client-side filtering is acceptable for small result sets (< 1000 items)
3. **Use `list_type_by_updated_at`** - Efficient for time-based queries with limited client-side filtering
4. **Non-versioned for high-volume filtering** - Consider using non-versioned resources when you need extensive DynamoDB-side filtering
#### Pagination Handling
For UI-driven pagination or batch processing:
```python
# First page
page1 = memory.paginated_dynamodb_query(
resource_class=MyResource,
results_limit=20
)
# Get pagination key for next page
if page1.has_more:
next_key = page1.pagination_key
# Fetch next page
page2 = memory.paginated_dynamodb_query(
resource_class=MyResource,
results_limit=20,
pagination_key=next_key
)
```
#### Query by Updated Time
Special helper for time-based queries:
```python
# List resources by most recently updated
recent = memory.list_type_by_updated_at(
MyResource,
results_limit=50,
filter_expression=Attr("status").eq("pending")
)
```
### Version Management
```python
# Get all versions of a resource
versions = memory.get_resource_versions(resource_id, MyResource)
# Get specific version
v2 = memory.read_existing_version(resource_id, MyResource, version=2)
# Version limits automatically enforced during updates
# (configure via resource_config['max_versions'])
```
## Code Quality Standards
- **Line length**: 120 characters
- **Python version**: ≥3.10
- **Type hints**: Required for all public APIs
- **Documentation**: Docstrings for classes and complex methods
- **Testing**: High coverage requirements (see `pyproject.toml`)
## Key Files and Their Purposes
- `src/simplesingletable/__init__.py` - Public API exports
- `src/simplesingletable/models.py` - Core resource classes and types
- `src/simplesingletable/dynamodb_memory.py` - Main DynamoDB interface
- `src/simplesingletable/utils.py` - Utility functions
- `src/simplesingletable/extras/` - Additional patterns and examples
- `tests/` - Comprehensive test suite
- `tasks.py` - Development automation scripts
- `pyproject.toml` - Project configuration and dependencies
## Common Gotchas and Considerations
1. **Versioned vs Non-versioned**: Choose based on whether you need history tracking
2. **Compression**: Versioned resources use compression by default; configure via `resource_config`
3. **GSI Limits**: DynamoDB has GSI limits; design access patterns carefully
4. **Version Limits**: Set `max_versions` to prevent unbounded growth
5. **Pagination**: Use `exhaust_pagination()` for complete result sets
6. **Concurrency**: Versioned resources prevent concurrent updates from same version
Raw data
{
"_id": null,
"home_page": null,
"name": "simplesingletable",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.10",
"maintainer_email": null,
"keywords": "dynamodb, singletabledesign",
"author": null,
"author_email": "Sully <sully@sadburger.com>",
"download_url": "https://files.pythonhosted.org/packages/35/93/84b88ea809aa9ce2fecca1ff833bf16820507396ede52b0291db8b294fea/simplesingletable-12.3.0.tar.gz",
"platform": null,
"description": "# Simple Single Table\n\n**Latest Version:** 12.3.0\n\n## Project Overview\n\n**simplesingletable** is a Python library providing an abstraction layer for AWS DynamoDB operations, specifically\ndesigned for single-table design patterns. The library uses Pydantic for model definitions and includes \"\nbatteries-included\" functionality for common DynamoDB use cases.\n\n### Target Use Cases\n\n- Small to medium scale applications\n- Single-table DynamoDB design patterns\n- Applications requiring versioned resources with automatic history tracking\n- Fast, consistent, and cost-effective storage solutions\n\n## Key Features\n\n1. **Single Table Design**: Store different object types in a single DynamoDB table\n2. **Automatic ID Generation**: Uses lexicographically sortable IDs via `ulid-py` for chronological ordering\n3. **Resource Versioning**: Automatic versioning with complete history and optimistic concurrency control\n4. **Secondary Access Patterns**: Support for GSI-based queries and filtering\n5. **Pydantic Integration**: Type-safe models with validation\n6. **Compression Support**: Optional gzip compression for large data\n\n## Architecture\n\n### Core Components\n\n#### Models (`src/simplesingletable/models.py`)\n\n- **`BaseDynamoDbResource`**: Abstract base class for all resources\n- **`DynamoDbResource`**: Non-versioned resources (simpler, lighter)\n- **`DynamoDbVersionedResource`**: Versioned resources with automatic history tracking\n- **`PaginatedList`**: Enhanced list for paginated query results\n\n#### Memory Layer (`src/simplesingletable/dynamodb_memory.py`)\n\n- **`DynamoDbMemory`**: Main interface for DynamoDB operations (CRUD, queries, filtering)\n\n#### Utilities (`src/simplesingletable/utils.py`)\n\n- ID generation, pagination helpers, DynamoDB type marshalling\n\n### Resource Types\n\n**Non-Versioned Resources** (`DynamoDbResource`):\n\n- Lighter weight, direct updates\n- No version history\n- Fields: `resource_id`, `created_at`, `updated_at`\n\n**Versioned Resources** (`DynamoDbVersionedResource`):\n\n- Complete version history\n- Optimistic concurrency control\n- Version limit enforcement (configurable via `max_versions`)\n- Compressed data storage by default\n- Fields: `resource_id`, `version`, `created_at`, `updated_at`\n\n### GSI (Global Secondary Index) Configuration\n\nThe library supports both static and dynamic GSI configuration:\n\n```python\nclass MyResource(DynamoDbVersionedResource):\n # Static configuration\n gsi_config: ClassVar[Dict[str, IndexFieldConfig]] = {\n \"status\": {\n \"pk\": lambda self: f\"status#{self.status}\",\n \"sk\": lambda self: self.resource_id, # Sort by creation time\n }\n }\n\n # Or dynamic configuration via get_gsi_config()\n @classmethod\n def get_gsi_config(cls) -> Dict[str, IndexFieldConfig]:\n return {\"dynamic_index\": {\"pk\": lambda self: f\"type#{self.type}\"}}\n```\n\n### Extras Module (`src/simplesingletable/extras/`)\n\nAdditional patterns and utilities:\n\n- **Repository Pattern**: `ResourceRepository` for higher-level operations\n- **Versioned Repository**: `VersionedResourceRepository` with version management\n- **Singleton Pattern**: For configuration resources\n- **Form Data**: Streamlit integration helpers\n- **Habit Tracker**: Example application\n\n## Development Setup\n\n### Dependencies\n\n**Core Dependencies**:\n\n- `boto3` - AWS SDK\n- `pydantic>2` - Data validation and serialization\n- `ulid-py` - Unique ID generation\n- `humanize` - Human-readable formatting\n\n**Development Dependencies**:\n\n- `pytest` + `pytest-cov` + `pytest-docker` - Testing framework\n- `black` + `isort` + `ruff` - Code formatting and linting\n- `invoke` - Task automation\n- `bumpver` - Version management\n\n### Build and Test Commands\n\nThe project uses `invoke` for task automation (`tasks.py`):\n\n```bash\n# Dependency management\ninv compile-requirements # Compile requirements.txt from pyproject.toml\ninv compile-requirements --upgrade # Update dependencies\n\n# Development\ninv lint # Format and lint code (black, isort, ruff)\ninv launch-dynamodb-local # Start local DynamoDB for testing\ninv halt-dynamodb-local # Stop local DynamoDB\n\n# Testing\npytest # Run test suite with coverage\npytest tests/test_specific.py # Run specific tests\n\n# Release management\ninv bumpver --patch|minor|major # Bump version\ninv build # Build distribution packages\ninv fullrelease --patch # Complete release cycle (lint, test, bump, build, publish)\n```\n\n### Testing Architecture\n\n- **Docker-based**: Uses DynamoDB Local via Docker Compose\n- **Pytest fixtures**: `conftest.py` provides database setup/teardown\n- **Comprehensive coverage**: Tests for CRUD, versioning, GSI, filtering\n- **Test files**:\n - `test_simplesingletable.py` - Core functionality\n - `test_versioned_repository.py` - Version management\n - `test_repository.py` - Repository pattern\n - `test_filter_expressions.py` - Query filtering\n\n## Important Patterns and Conventions\n\n### Resource Definition Pattern\n\n```python\nfrom simplesingletable import DynamoDbVersionedResource\n\n\nclass MyResource(DynamoDbVersionedResource):\n name: str\n status: str\n metadata: Optional[dict] = None\n \n # the following are defined on the base class\n # resource_id: str\n # version: int\n # created_at: datetime\n # updated_at: datetime\n\n # Optional: Configure compression and version limits\n resource_config: ClassVar[ResourceConfig] = ResourceConfig(\n compress_data=True, # Default for versioned\n max_versions=10 # Keep only 10 versions\n )\n\n # Optional: GSI configuration for secondary access patterns\n def db_get_gsi1pk(self) -> str | None:\n return f\"status#{self.status}\"\n```\n\n### CRUD Operations Pattern\n\n```python\nfrom simplesingletable import DynamoDbMemory\n\nmemory = DynamoDbMemory(logger=logger, table_name=\"my-table\")\n\n# Create\nresource = memory.create_new(MyResource, {\"name\": \"test\", \"status\": \"active\"})\n\n# Read\nretrieved = memory.read_existing(resource.resource_id, MyResource)\n\n# Update (versioned resources automatically increment version)\nupdated = memory.update_existing(retrieved, {\"status\": \"inactive\"})\n\n# List with filtering\nresources = memory.list_resources(\n MyResource,\n filter_fn=lambda r: r.status == \"active\",\n limit=50\n)\n```\n\n### Paginated Queries\n\nThe library provides powerful paginated query capabilities for efficient data retrieval from DynamoDB, supporting both primary key and GSI-based queries with filtering.\n\n#### Basic Paginated Query\n\n```python\nfrom simplesingletable import exhaust_pagination\n\n# Direct paginated query with GSI\nresults = memory.paginated_dynamodb_query(\n resource_class=MyResource,\n index_name=\"gsi1\",\n key_condition=Key(\"gsi1pk\").eq(\"status#active\"),\n results_limit=100, # Items per page\n pagination_key=None # For first page\n)\n\n# Get all results (automatically handles pagination)\nall_results = []\nfor page in exhaust_pagination(\n lambda pk=None: memory.paginated_dynamodb_query(\n resource_class=MyResource,\n index_name=\"gsi1\", \n key_condition=Key(\"gsi1pk\").eq(\"status#active\"),\n results_limit=100,\n pagination_key=pk\n )\n):\n all_results.extend(page)\n```\n\n#### Query Patterns with GSI\n\nDefine query methods on your resource classes for reusable access patterns:\n\n```python\nclass MyResource(DynamoDbVersionedResource):\n status: str\n category: str\n \n def db_get_gsi1pk(self) -> str | None:\n # Sparse GSI - only index active items\n if self.status == \"active\":\n return f\"{self.get_unique_key_prefix()}#{self.category}\"\n return None\n \n @classmethod\n def query_by_category_kwargs(cls, category: str):\n \"\"\"Build query kwargs for category-based queries.\"\"\"\n return {\n \"index_name\": \"gsi1\",\n \"key_condition\": Key(\"gsi1pk\").eq(f\"{cls.get_unique_key_prefix()}#{category}\")\n }\n\n# Use the query pattern\nactive_in_category = memory.paginated_dynamodb_query(\n resource_class=MyResource,\n **MyResource.query_by_category_kwargs(\"important\"),\n results_limit=50\n)\n```\n\n#### Advanced Filtering\n\n**Important Note on Versioned Resources**: Versioned resources use compression by default, which means DynamoDB filter expressions can only access attributes that are part of a GSI (pk, sk, gsi1pk, gsi1sk, etc.). For compressed resources, attribute-based filtering must be done client-side. Non-versioned resources don't have this limitation.\n\n```python\nfrom boto3.dynamodb.conditions import Attr\n\n# For NON-VERSIONED resources - DynamoDB-side filtering works\nfilter_expression = Attr(\"status\").eq(\"active\") & Attr(\"priority\").gt(5)\n\n# For VERSIONED resources - use client-side filtering or GSI design\ndef custom_filter(resource: MyVersionedResource) -> bool:\n return resource.status == \"active\" and resource.priority > 5\n\n# Combined approach (versioned resource example)\nresults = memory.paginated_dynamodb_query(\n resource_class=MyVersionedResource,\n filter_fn=custom_filter, # Client-side only for versioned\n results_limit=100\n)\n```\n\n**Best Practices for Efficient Filtering**:\n1. **Design GSIs carefully** - For versioned resources, encode filterable attributes in GSI keys\n2. **Consider data volume** - Client-side filtering is acceptable for small result sets (< 1000 items)\n3. **Use `list_type_by_updated_at`** - Efficient for time-based queries with limited client-side filtering\n4. **Non-versioned for high-volume filtering** - Consider using non-versioned resources when you need extensive DynamoDB-side filtering\n\n#### Pagination Handling\n\nFor UI-driven pagination or batch processing:\n\n```python\n# First page\npage1 = memory.paginated_dynamodb_query(\n resource_class=MyResource,\n results_limit=20\n)\n\n# Get pagination key for next page\nif page1.has_more:\n next_key = page1.pagination_key\n \n # Fetch next page\n page2 = memory.paginated_dynamodb_query(\n resource_class=MyResource,\n results_limit=20,\n pagination_key=next_key\n )\n```\n\n#### Query by Updated Time\n\nSpecial helper for time-based queries:\n\n```python\n# List resources by most recently updated\nrecent = memory.list_type_by_updated_at(\n MyResource,\n results_limit=50,\n filter_expression=Attr(\"status\").eq(\"pending\")\n)\n```\n\n### Version Management\n\n```python\n# Get all versions of a resource\nversions = memory.get_resource_versions(resource_id, MyResource)\n\n# Get specific version\nv2 = memory.read_existing_version(resource_id, MyResource, version=2)\n\n# Version limits automatically enforced during updates\n# (configure via resource_config['max_versions'])\n```\n\n## Code Quality Standards\n\n- **Line length**: 120 characters\n- **Python version**: \u22653.10\n- **Type hints**: Required for all public APIs\n- **Documentation**: Docstrings for classes and complex methods\n- **Testing**: High coverage requirements (see `pyproject.toml`)\n\n## Key Files and Their Purposes\n\n- `src/simplesingletable/__init__.py` - Public API exports\n- `src/simplesingletable/models.py` - Core resource classes and types\n- `src/simplesingletable/dynamodb_memory.py` - Main DynamoDB interface\n- `src/simplesingletable/utils.py` - Utility functions\n- `src/simplesingletable/extras/` - Additional patterns and examples\n- `tests/` - Comprehensive test suite\n- `tasks.py` - Development automation scripts\n- `pyproject.toml` - Project configuration and dependencies\n\n## Common Gotchas and Considerations\n\n1. **Versioned vs Non-versioned**: Choose based on whether you need history tracking\n2. **Compression**: Versioned resources use compression by default; configure via `resource_config`\n3. **GSI Limits**: DynamoDB has GSI limits; design access patterns carefully\n4. **Version Limits**: Set `max_versions` to prevent unbounded growth\n5. **Pagination**: Use `exhaust_pagination()` for complete result sets\n6. **Concurrency**: Versioned resources prevent concurrent updates from same version\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "A simple boto3/Pydantic implementation of DynamoDB Single Table Design and related utilities.",
"version": "12.3.0",
"project_urls": {
"Homepage": "https://github.com/msull/simplesingletable"
},
"split_keywords": [
"dynamodb",
" singletabledesign"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "5f7cf45f72a1369a407a8d5f7d7d237bcf8a8d09fbbd722bc2204d24981864bc",
"md5": "daa69c33e123671e4518088ff0963296",
"sha256": "7a1d0af05b872e48e0931ea107548b13ae5ab341a70adcb3a7743115ccb71193"
},
"downloads": -1,
"filename": "simplesingletable-12.3.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "daa69c33e123671e4518088ff0963296",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.10",
"size": 50862,
"upload_time": "2025-09-16T16:19:23",
"upload_time_iso_8601": "2025-09-16T16:19:23.189819Z",
"url": "https://files.pythonhosted.org/packages/5f/7c/f45f72a1369a407a8d5f7d7d237bcf8a8d09fbbd722bc2204d24981864bc/simplesingletable-12.3.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "359384b88ea809aa9ce2fecca1ff833bf16820507396ede52b0291db8b294fea",
"md5": "b3611b7b8c43f8ee9dabe593bb6d4216",
"sha256": "29de1f9635717e67659ce2bb2ecc2cd34ffa380b4d5982b906cec5fea075b92a"
},
"downloads": -1,
"filename": "simplesingletable-12.3.0.tar.gz",
"has_sig": false,
"md5_digest": "b3611b7b8c43f8ee9dabe593bb6d4216",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.10",
"size": 87404,
"upload_time": "2025-09-16T16:19:24",
"upload_time_iso_8601": "2025-09-16T16:19:24.350918Z",
"url": "https://files.pythonhosted.org/packages/35/93/84b88ea809aa9ce2fecca1ff833bf16820507396ede52b0291db8b294fea/simplesingletable-12.3.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-09-16 16:19:24",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "msull",
"github_project": "simplesingletable",
"travis_ci": false,
"coveralls": false,
"github_actions": false,
"requirements": [],
"lcname": "simplesingletable"
}