A Python implementation of the Embedded Sass Host, providing a Python API for the Dart Sass compiler using the embedded protocol.
- 🚀 Modern Architecture - Uses Dart Sass embedded protocol for compilation
- 🔄 Async Support - Both synchronous and asynchronous APIs
- 🎯 Custom Functions - Register Python functions callable from Sass
- 📁 Custom Importers - Handle custom import logic in Python
- 🛠️ Full Sass Support - Complete Sass language feature support via Dart Sass
- 🧪 Well Tested - Comprehensive test suite with 278 passing tests
This package requires the Dart Sass compiler to be installed on your system. You have several options:
Download the standalone Dart Sass binary from the official releases:
# Linux x64
wget https://github.com/sass/dart-sass/releases/download/1.81.0/dart-sass-1.81.0-linux-x64.tar.gz
tar -xzf dart-sass-1.81.0-linux-x64.tar.gz
sudo mv dart-sass /usr/local/bin/
# macOS x64
wget https://github.com/sass/dart-sass/releases/download/1.81.0/dart-sass-1.81.0-macos-x64.tar.gz
tar -xzf dart-sass-1.81.0-macos-x64.tar.gz
sudo mv dart-sass /usr/local/bin/
# Windows x64
# Download dart-sass-1.81.0-windows-x64.zip and extract to your PATH
# Homebrew (macOS/Linux)
brew install sass/sass/sass
# Chocolatey (Windows)
choco install sass
# npm (if you have Node.js)
npm install -g sass
If you install Dart Sass to a custom location, set the environment variable:
export SASS_EMBEDDED_COMPILER_PATH="/path/to/your/sass"
Note: This package is not yet published to PyPI. To use it, you'll need to install from source or wait for the official release.
# Install from source (when available)
pip install git+https://github.com/hmcqueen/python-dart-sass.git
# Or install locally for development
pip install -e .
# When published to PyPI (future):
pip install python-dart-sass
import dart_sass as sass
# Compile from string
result = sass.compile_string("""
$primary: #007bff;
.button {
background-color: $primary;
padding: 0.5rem 1rem;
}
""")
print(result.css)
# Compile from file
result = sass.compile('styles.scss')
print(result.css)
import dart_sass as sass
import asyncio
async def compile_sass():
result = await sass.compile_async('styles.scss')
print(result.css)
asyncio.run(compile_sass())
Register Python functions that can be called from Sass:
import dart_sass as sass
from dart_sass.value import SassString, SassNumber
def pow_function(args):
"""Calculate power: pow($base, $exponent)"""
base = args[0].assert_number().value
exponent = args[1].assert_number().value
return SassNumber(base ** exponent)
# Compile with custom function
result = sass.compile_string("""
.element {
width: pow(2, 3) * 1px; // Results in 8px
}
""", functions={
'pow($base, $exponent)': pow_function
})
Handle custom import logic:
import dart_sass as sass
from dart_sass.importer import Importer, ImportResult
class ThemeImporter(Importer):
def canonicalize(self, url, context):
if url.startswith('theme:'):
return f'file:///themes/{url[6:]}.scss'
return None
def load(self, canonical_url):
# Load theme file content
theme_name = canonical_url.split('/')[-1].replace('.scss', '')
content = f"$theme: '{theme_name}';"
return ImportResult(content, syntax='scss')
result = sass.compile_string("""
@import 'theme:dark';
.app { color: $theme; }
""", importers=[ThemeImporter()])
For simpler file-based imports:
from dart_sass.importer import FileImporter
class NodeModulesImporter(FileImporter):
def find_file_url(self, url, context):
if not url.startswith('~'):
return None
# Handle npm-style imports: @import '~bootstrap/scss/bootstrap'
package_path = url[1:] # Remove ~
file_path = f'node_modules/{package_path}'
if os.path.exists(f'{file_path}.scss'):
return f'file://{os.path.abspath(file_path)}.scss'
return None
result = sass.compile_string("""
@import '~bootstrap/scss/variables';
""", importers=[NodeModulesImporter()])
The package provides Python representations of all Sass value types:
from dart_sass.value import *
# Numbers
num = SassNumber(42, unit='px')
print(num.value) # 42
print(num.unit) # 'px'
# Strings
string = SassString('hello world', quoted=True)
print(string.text) # 'hello world'
print(string.quoted) # True
# Colors
color = SassColor.rgb(255, 0, 0) # Red
print(color.red) # 255
print(color.green) # 0
print(color.blue) # 0
# Lists
sass_list = SassList([
SassNumber(1),
SassNumber(2),
SassNumber(3)
], separator=',')
# Maps
sass_map = SassMap({
SassString('primary'): SassColor.rgb(0, 123, 255),
SassString('secondary'): SassColor.rgb(108, 117, 125)
})
# Booleans and null
sass_true = SassBoolean.sass_true
sass_false = SassBoolean.sass_false
sass_null = SassNull.sass_null
result = sass.compile_string(scss_content, {
# Output style
'style': 'compressed', # 'expanded', 'compressed'
# Source maps
'source_map': True,
# Load paths for @import
'load_paths': ['/path/to/sass', '/another/path'],
# Custom functions
'functions': {
'custom-function($arg)': my_function
},
# Custom importers
'importers': [MyImporter()],
# Charset handling
'charset': True,
# Quiet dependency warnings
'quiet_deps': True,
# Verbose output
'verbose': False
})
print(result.css)
print(result.source_map) # If source_map=True
print(result.loaded_urls) # List of imported file URLs
from dart_sass.exception import CompileException
try:
result = sass.compile_string("""
.invalid {
color: $undefined-variable;
}
""")
except CompileException as e:
print(f"Compilation failed: {e}")
# Exception includes detailed error information with line numbers
The asynchronous API may be beneficial for multiple compilations since it runs Dart Sass in a separate process:
import asyncio
import dart_sass as sass
async def compile_multiple():
tasks = [
sass.compile_async('file1.scss'),
sass.compile_async('file2.scss'),
sass.compile_async('file3.scss')
]
results = await asyncio.gather(*tasks)
return results
# This allows concurrent compilation vs sequential sync API
results = asyncio.run(compile_multiple())
For multiple compilations, you can manage the compiler lifecycle to avoid repeated initialization:
# Initialize once, use multiple times
compiler = sass.init_async_compiler()
try:
result1 = await compiler.compile_string(scss1)
result2 = await compiler.compile_string(scss2)
result3 = await compiler.compile_string(scss3)
finally:
await compiler.dispose() # Clean up resources
This package implements the Embedded Sass Protocol to communicate with Dart Sass:
- Protocol Communication: Uses protocol buffers over stdin/stdout
- Message Handling: Reactive streams for handling compilation requests/responses
- Value Conversion: Bidirectional conversion between Python and Sass value types
- Process Management: Manages Dart Sass subprocess lifecycle
- Cross-Platform: Works on Linux, macOS, and Windows
- Compilers:
AsyncCompiler
andSyncCompiler
for different usage patterns - Value System: Complete Sass value type implementations
- Protocol Layer: Message encoding/decoding and transport
- Importers: File and custom importer interfaces
- Functions: Custom function registration and calling
This project uses uv
for dependency management:
# Install dependencies
uv sync
# Run tests
uv run pytest
# Run with coverage
uv run pytest --cov=sass_embedded
# Linting and formatting
uv run ruff check
uv run black --check .
uv run isort --check-only .
# Format code
uv run black .
uv run isort .
The test suite includes:
- Unit tests for all value types
- Integration tests with real Sass compilation
- Protocol communication tests
- Cross-platform compatibility tests
# Run specific test categories
uv run pytest tests/test_values.py # Value type tests
uv run pytest tests/test_compiler.py # Compiler tests
uv run pytest tests/test_protocol.py # Protocol tests
- Python: 3.10+
- Dart Sass: 1.45.0+ (embedded protocol support)
- Platforms: Linux, macOS, Windows
- Architecture: x86_64, ARM64
Feature | python-dart-sass | libsass-python | pysass |
---|---|---|---|
Sass Version | Latest Dart Sass | LibSass (deprecated) | LibSass |
Architecture | Separate process | Native extension | Native extension |
Async Support | ✅ | ❌ | ❌ |
Custom Functions | ✅ | ✅ | ✅ |
Custom Importers | ✅ | ✅ | Limited |
Source Maps | ✅ | ✅ | ✅ |
Active Development | ✅ | ❌ | ❌ |
MIT License
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature
) - Make your changes
- Add tests for new functionality
- Ensure all tests pass (
uv run pytest
) - Commit your changes (
git commit -m 'Add amazing feature'
) - Push to the branch (
git push origin feature/amazing-feature
) - Open a Pull Request
This Python implementation was developed by Harvey McQueen with assistance from Amazon Q Command-line. It is:
- Based on the Embedded Sass Protocol
- Inspired by the Node.js Embedded Sass Host
- Uses Dart Sass as the compilation engine
This is an independent Python implementation and is not affiliated with or endorsed by the official Sass team.