Custom Exporters
Palettize’s exporter system is extensible. You can create custom exporters for your specific output formats and distribute them as plugins.
Creating an Exporter
Section titled “Creating an Exporter”All exporters inherit from BaseExporter and implement required methods.
Basic Structure
Section titled “Basic Structure”from typing import Any, ClassVarfrom collections.abc import Mapping
from palettize import Colormap, Opt, OptionSpec, ScalingFunctionfrom palettize.exporters import BaseExporter
class MyExporter(BaseExporter): options: ClassVar[OptionSpec] = OptionSpec( Opt("num_colors", int, 10, "Number of color steps.", minimum=2), )
@property def identifier(self) -> str: """Unique identifier used in CLI and get_exporter()""" return "myformat"
@property def name(self) -> str: """Human-readable name""" return "My Custom Format"
@property def default_file_extension(self) -> str | None: """Default file extension (without dot)""" return "txt"
def export( self, colormap: Colormap, scaler: ScalingFunction, domain_min: float, domain_max: float, options: Mapping[str, Any] | None = None, ) -> str: """Generate output string""" num_colors = self.resolve_options(options)["num_colors"]
lines = [] for i in range(num_colors): position = i / (num_colors - 1) if num_colors > 1 else 0.5 data_val = domain_min + position * (domain_max - domain_min) color = colormap.apply_scaler(data_val, scaler) lines.append(f"{data_val}: {color}")
return "\n".join(lines)Declare every option as an Opt. That declaration coerces CLI strings (-O num_colors=256), rejects bad values, and is what palettize formats myformat prints. Read the resolved dict inside export; do not call options.get on the raw mapping.
Required Methods
Section titled “Required Methods”| Method | Type | Description |
|---|---|---|
identifier | property | Unique string ID |
name | property | Display name |
export() | method | Generate output string |
Optional Methods
Section titled “Optional Methods”| Method | Type | Default | Description |
|---|---|---|---|
default_file_extension | property | None | Suggested file extension |
Registering an Exporter
Section titled “Registering an Exporter”Local Registration
Section titled “Local Registration”For use within your project:
from palettize import register_exporter
exporter = MyExporter()register_exporter(exporter)
# Now available via get_exporterfrom palettize import get_exportermy_exp = get_exporter("myformat")Overwriting Built-ins
Section titled “Overwriting Built-ins”To replace an existing exporter:
register_exporter(exporter, overwrite=True)Creating a Plugin Package
Section titled “Creating a Plugin Package”Distribute your exporter as an installable package.
Package Structure
Section titled “Package Structure”my-palettize-exporters/├── pyproject.toml├── src/│ └── my_exporters/│ ├── __init__.py│ └── formats.pyDefine the Exporter
Section titled “Define the Exporter”src/my_exporters/formats.py:
from typing import Any, ClassVarfrom collections.abc import Mapping
from palettize import Colormap, Opt, OptionSpec, ScalingFunctionfrom palettize.exporters import BaseExporter
class CSVExporter(BaseExporter): options: ClassVar[OptionSpec] = OptionSpec( Opt("num_colors", int, 256, "Number of color steps.", minimum=2), )
@property def identifier(self) -> str: return "csv"
@property def name(self) -> str: return "CSV Color Table"
@property def default_file_extension(self) -> str | None: return "csv"
def export( self, colormap: Colormap, scaler: ScalingFunction, domain_min: float, domain_max: float, options: Mapping[str, Any] | None = None, ) -> str: num_colors = self.resolve_options(options)["num_colors"]
lines = ["value,r,g,b,a"] for i in range(num_colors): pos = i / (num_colors - 1) data_val = domain_min + pos * (domain_max - domain_min) rgba = colormap.apply_scaler(data_val, scaler, output_format="rgba_tuple") r, g, b, a = rgba lines.append(f"{data_val},{r},{g},{b},{a}")
return "\n".join(lines)Register via Entry Points
Section titled “Register via Entry Points”pyproject.toml:
[project]name = "my-palettize-exporters"version = "0.1.0"dependencies = ["palettize"]
[project.entry-points."palettize.exporters"]csv = "my_exporters.formats:CSVExporter"Install and Use
Section titled “Install and Use”pip install my-palettize-exporters
# Now available in CLIpalettize list exporters # Shows "csv"palettize create viridis -f csv -o colors.csv --domain 0,255Advanced Example: XML Exporter
Section titled “Advanced Example: XML Exporter”import xml.etree.ElementTree as ETfrom typing import Any, ClassVarfrom collections.abc import Mapping
from palettize import Colormap, Opt, OptionSpec, ScalingFunctionfrom palettize.exporters import BaseExporter
class XMLColorTableExporter(BaseExporter): options: ClassVar[OptionSpec] = OptionSpec( Opt("num_colors", int, 256, "Number of color steps.", minimum=2), Opt("name", str, None, "Table name. Defaults to the colormap name."), )
@property def identifier(self) -> str: return "xmlct"
@property def name(self) -> str: return "XML Color Table"
@property def default_file_extension(self) -> str | None: return "xml"
def export( self, colormap: Colormap, scaler: ScalingFunction, domain_min: float, domain_max: float, options: Mapping[str, Any] | None = None, ) -> str: opts = self.resolve_options(options) num_colors = opts["num_colors"] cmap_name = opts["name"] or colormap.name or "colormap"
root = ET.Element("ColorTable", name=cmap_name) root.set("domain_min", str(domain_min)) root.set("domain_max", str(domain_max))
for i in range(num_colors): pos = i / (num_colors - 1) data_val = domain_min + pos * (domain_max - domain_min) hex_color = colormap.apply_scaler(data_val, scaler)
entry = ET.SubElement(root, "Entry") entry.set("value", f"{data_val:.4f}") entry.set("color", hex_color)
ET.indent(root, space=" ") return ET.tostring(root, encoding="unicode", xml_declaration=True)Declaring Options
Section titled “Declaring Options”Opt is the validation. A bool option accepts true/false from the CLI, an int rejects 5.5, and choices limits a string to a set. palettize formats <id> prints help, the type, and the default.
from palettize import Opt, OptionSpec
options = OptionSpec( Opt("num_colors", int, 256, "Number of colors.", minimum=2), Opt("include_header", bool, True, "Include a header line."), Opt("precision", int, 2, "Decimal places.", minimum=0),)
opts = self.resolve_options(options_from_caller)num_colors = opts["num_colors"]Testing Your Exporter
Section titled “Testing Your Exporter”import pytestfrom palettize import create_colormap, get_scaler_by_namefrom my_exporters.formats import CSVExporter
def test_csv_export(): cmap = create_colormap(preset="viridis") scaler = get_scaler_by_name("linear", domain_min=0, domain_max=100) exporter = CSVExporter()
output = exporter.export(cmap, scaler, 0, 100, {"num_colors": 5})
lines = output.strip().split("\n") assert lines[0] == "value,r,g,b,a" assert len(lines) == 6 # header + 5 colors
def test_csv_identifier(): exporter = CSVExporter() assert exporter.identifier == "csv" assert exporter.default_file_extension == "csv"Best Practices
Section titled “Best Practices”- Declare options with
Opt: types, ranges, and help text live in one place - Keep help to one line:
palettize formatsprints it as the option description - Handle edge cases: Empty colormaps, single color, etc.
- Use meaningful defaults: Most users won’t specify options
- Return strings: The
export()method always returns a string - Test thoroughly: Cover various colormap types and options