Resource discovery and resolution library for Python
Project description
JustMyResource
A precise, lightweight, and extensible resource discovery library for Python. JustMyResource provides a robust "Resource Atlas" for the Python ecosystem—a definitive map of every resource available to an application, whether bundled in a Python package or provided by third-party resource packs.
Features
- Generic Framework: Unified interface for multiple resource types (SVG icons, raster images, future: audio/video)
- Extensible: Resource packs can be added via standard Python EntryPoints mechanism
- Efficient: Lazy discovery with in-memory caching
- Prefix-Based Resolution: Namespace disambiguation via
pack:nameformat - Type-Safe: Returns
ResourceContentobjects with MIME types and metadata - Zero Dependencies: Core library has no required dependencies
Installation
pip install justmyresource
Quick Start
from justmyresource import ResourceRegistry, get_default_registry
# Get default registry
registry = get_default_registry()
# Get resource with fully qualified name (always unique)
content = registry.get_resource("acme-icons/lucide:lightbulb")
# Get resource with short pack name (works if unique)
content = registry.get_resource("lucide:lightbulb")
# Get resource with alias
content = registry.get_resource("luc:lightbulb")
# Check content type and use accordingly
if content.content_type == "image/svg+xml":
svg_text = content.text # Decode as UTF-8
# Use SVG text...
# Get resource without prefix (searches by priority)
content = registry.get_resource("logo")
Basic Usage
Getting Resources
from justmyresource import ResourceRegistry
registry = ResourceRegistry()
# Get resource with fully qualified name (always unique)
content = registry.get_resource("acme-icons/lucide:lightbulb")
# Get resource with short pack name (works if unique)
content = registry.get_resource("lucide:lightbulb")
# Get resource without prefix (searches packs by priority)
content = registry.get_resource("logo")
# Access resource data
if content.content_type == "image/svg+xml":
svg_text = content.text # UTF-8 decoded string
elif content.content_type == "image/png":
png_bytes = content.data # Raw bytes
Listing Resources
# List all resources from all packs
for resource_info in registry.list_resources():
print(f"{resource_info.pack}:{resource_info.name} ({resource_info.content_type})")
# pack is qualified name (e.g., "acme-icons/lucide")
# List resources from a specific pack (qualified or short name)
for resource_info in registry.list_resources(pack="acme-icons/lucide"):
print(resource_info.name)
for resource_info in registry.list_resources(pack="lucide"):
print(resource_info.name)
# List registered packs (returns qualified names)
for qualified_name in registry.list_packs():
print(qualified_name) # e.g., "acme-icons/lucide"
Blocking Resource Packs
# Block specific packs (accepts short or qualified names)
registry = ResourceRegistry(blocklist={"broken-pack", "acme-icons/lucide"})
# Block via environment variable
# RESOURCE_DISCOVERY_BLOCKLIST="broken-pack,acme-icons/lucide" python app.py
Handling Prefix Collisions
When multiple packs claim the same prefix, the registry emits warnings and resolves by priority:
import warnings
# Filter collision warnings if desired
warnings.filterwarnings("ignore", category=PrefixCollisionWarning)
registry = ResourceRegistry()
# Both packs remain accessible via qualified names
content1 = registry.get_resource("acme-icons/lucide:lightbulb")
content2 = registry.get_resource("cool-icons/lucide:lightbulb")
# Inspect collisions
collisions = registry.get_prefix_collisions()
# {"lucide": ["acme-icons/lucide", "cool-icons/lucide"]}
Custom Prefix Mapping
Override prefix mappings to resolve collisions or add custom aliases:
# Via constructor
registry = ResourceRegistry(
prefix_map={
"icons": "acme-icons/lucide", # Map "icons" to specific pack
"mi": "material-icons/core", # Custom alias
}
)
# Via environment variable
# RESOURCE_PREFIX_MAP="icons=acme-icons/lucide,mi=material-icons/core" python app.py
# Inspect current mappings
prefix_map = registry.get_prefix_map()
# {"icons": "acme-icons/lucide", "mi": "material-icons/core", ...}
Creating Resource Packs
Resource packs can be registered via Python EntryPoints. This allows applications to bundle resources or third-party packages to provide resources.
First-Party Resource Pack (Application's Own Resources)
# myapp/resources.py
from collections.abc import Iterator
from pathlib import Path
from importlib.resources import files
from justmyresource.types import ResourceContent, ResourcePack
class MyAppResourcePack:
"""Resource pack for application's bundled resources."""
def __init__(self):
package = files("myapp.resources")
self._base_path = Path(str(package))
def get_resource(self, name: str) -> ResourceContent:
"""Get resource from bundled files."""
# Try SVG first
svg_path = self._base_path / f"{name}.svg"
if svg_path.exists():
with open(svg_path, "rb") as f:
return ResourceContent(
data=f.read(),
content_type="image/svg+xml",
encoding="utf-8",
)
# Try PNG
png_path = self._base_path / f"{name}.png"
if png_path.exists():
with open(png_path, "rb") as f:
return ResourceContent(
data=f.read(),
content_type="image/png",
)
raise ValueError(f"Resource not found: {name}")
def list_resources(self) -> Iterator[str]:
"""List all resources."""
for path in self._base_path.iterdir():
if path.suffix in (".svg", ".png"):
yield path.stem
def get_priority(self) -> int:
return 100
def get_prefixes(self) -> list[str]:
return ["myapp"] # Optional aliases (pack name is auto-registered)
# myapp/__init__.py
def get_resource_provider():
"""Entry point factory for application's bundled resources."""
from myapp.resources import MyAppResourcePack
return MyAppResourcePack()
# pyproject.toml
[project.entry-points."justmyresource.packs"]
"myapp-resources" = "myapp:get_resource_provider"
Third-Party Resource Pack
# my_resource_pack/__init__.py
from my_resource_pack.provider import MyResourcePack
def get_resource_provider():
"""Entry point factory for resource pack."""
return MyResourcePack()
Architecture
JustMyResource follows a unified "Resource Pack" architecture where all resource sources implement the same ResourcePack protocol. This ensures:
- Consistency: All resources are discovered and resolved the same way
- Extensibility: New resource sources can be added via EntryPoints
- Priority: Resource packs (priority 100) override system resources (priority 0, future)
See docs/architecture.md for detailed architecture documentation.
ResourceContent Type
Resources are returned as ResourceContent objects:
@dataclass(frozen=True, slots=True)
class ResourceContent:
data: bytes # Raw resource bytes
content_type: str # MIME type: "image/svg+xml", "image/png", etc.
encoding: str | None = None # Encoding for text resources (e.g., "utf-8")
metadata: dict[str, Any] | None = None # Optional pack-specific metadata
@property
def text(self) -> str:
"""Decode data as text (raises if encoding is None)."""
...
This wrapper allows consumers to:
- Branch on
content_typeto handle different resource types - Access text content via
.textproperty for text-based resources - Access pack-specific metadata via
.metadatadict - Handle mixed-format packs (e.g., a samples pack with both SVG and PNG)
Development
Setup
# Clone the repository
git clone https://github.com/kws/justmyresource.git
cd justmyresource
# Install with development dependencies
pip install -e ".[dev]"
Running Tests
# Run tests with coverage
pytest
# Run with coverage report
pytest --cov=justmyresource --cov-report=html
Code Quality
# Format code
ruff format .
# Lint code
ruff check .
# Type checking
mypy src/
Requirements
- Python 3.10+
- No required dependencies (core library)
- Resource packs may have their own dependencies
License
MIT License - see LICENSE file for details.
Contributing
Contributions are welcome! Please read the architecture documentation in docs/architecture.md and follow the project philosophy outlined in AGENTS.md.
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 justmyresource-0.1.0.tar.gz.
File metadata
- Download URL: justmyresource-0.1.0.tar.gz
- Upload date:
- Size: 13.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/2.1.2 CPython/3.10.6 Darwin/24.6.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cee770ce7042e67104f2f7e029208e5b68ddf67cb8ea5f7bbe699704d346b04d
|
|
| MD5 |
6f02b0136a60fa89678350c46338aa01
|
|
| BLAKE2b-256 |
3636a28116991ee5ba0dca93458c2219771295eaaaa6992e2346b3249356d304
|
File details
Details for the file justmyresource-0.1.0-py3-none-any.whl.
File metadata
- Download URL: justmyresource-0.1.0-py3-none-any.whl
- Upload date:
- Size: 12.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/2.1.2 CPython/3.10.6 Darwin/24.6.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
853864d194d1de1f280df5d2e05afc380630c24681c012343ac2aeb9c4c286a7
|
|
| MD5 |
8c2101a7b2202c67c3873ef253e06b5e
|
|
| BLAKE2b-256 |
cd70325bc7436135b6259511d780790fedb098c132c03fb3a353433755e86b0d
|