<jupyter_start><jupyter_text>The above config contains an unknown track type to `hg`, so we get a validation error because we don't know how to render the track!<jupyter_code>import hg
hg.Viewconf(**config) # oh no, track not recognized!<jupyter_output><empty_output><jupyter_text>`hg.PluginTrack`The `hg.PluginTrack` provides a mechanism to hook into the schema validation as well as provide the plugin source for the renderer. The `plugin_url` is a special field which is a `ClassVar` and ignored by pydantic for serde/validation. In `hg` a plugin can by created by subclassing `hg.PluginTrack` and specifying the `type` and `plugin_url`.<jupyter_code>from typing import ClassVar, Union
from typing_extensions import Literal

class PileupTrack(hg.PluginTrack):
    type: Literal['pileup']
    plugin_url: ClassVar[str] = "https://unpkg.com/higlass-pileup/dist/higlass-pileup.min.js"
        
hg.Viewconf[Union[PileupTrack, hg.Track]](**config) # works!<jupyter_output><empty_output><jupyter_text>How does this work? The `hg.Viewconf` is a `pydantic.GenericModel` which is _generic_ over the track type. By default, only HiGlass's builtin track types are recognized, so `hg.Viewconf(**data)` will throw an error when a configuration contains an unknown track.By supplying a our plugin track as a type parameter explicity, `hg.Viewconf[Union[PileupTrack, hg.Track]]`, we extend the model to recognize the `PileupTrack` in our config.This can seem a bit verbose, but supplying the type parameter explicitly is only necessary when deserializing an unknown config, e.g.```pythonhg.Viewconf.parse_file('./pileup-example.json')  errorhg.Viewconf[PileupTrack].parse_file('./pileup-example.json')  works!```The `hg.track`, `hg.view`, and `hr.viewconf` utils _infer_ these types so you don't need to supply them!<jupyter_code>import hg
from typing_extensions import Literal
from typing import ClassVar
class MultivecTrack(hg.PluginTrack):
    type: Literal[
        "basic-multiple-line-chart",
        "horizontal-stacked-bar",
        "basic-multiple-bar-chart",
    ]
    plugin_url: ClassVar[str] = "https://unpkg.com/higlass-multivec/dist/higlass-multivec.min.js"
import hg
from typing_extensions import Literal
from typing import ClassVar

class MultivecTrack(hg.PluginTrack):
    type: Literal[
        "basic-multiple-line-chart",
        "horizontal-stacked-bar",
        "basic-multiple-bar-chart",
    ]
    plugin_url: ClassVar[str] = "https://unpkg.com/higlass-multivec/dist/higlass-multivec.min.js"
        
track = MultivecTrack(**{
    "type": "horizontal-stacked-bar",
    "tilesetUid": "my-multivec-db",
    "server": "http://test1.resgen.io/api/v1",
    "height": 200,
    "width": 470,
    "options": {
        "labelPosition": "topLeft",
        "labelColor": "black",
        "labelTextOpacity": 0.4,
        "valueScaling": "exponential",
        "trackBorderWidth": 0,
        "trackBorderColor": "black",
        "heatmapValueScaling": "log",
        "name": "all.KL.bed.multires.mv5",
        "scaledHeight": True,
        "backgroundColor": "white",
        "sortLargestOnTop": True,
    },
})

track2 = PileupTrack(**config["views"][0]["tracks"]["top"][0])

hg.view((track, "top"), (track2, "top")) # types inferred!<jupyter_output><empty_output><jupyter_text>Extending plugins with `pydantic`A `type` and `plugin_url` are minimally what is required to implement a plugin track, however, plugin may define additional fields using pydantic models, granting finer control over serde and validation.Below we define the `data` field on the `SequenceTrack` using a custom pydantic model.<jupyter_code>from pydantic import BaseModel, Extra
from typing import Optional

class SeqeuenceTrackData(BaseModel):
    class Config:
        extra = Extra.forbid
    type: Literal["fasta"]
    fastaUrl: str
    faiUrl: str
    chromSizesUrl: str
        
class SequenceTrack(hg.PluginTrack):
    type: Literal["horizontal-sequence"]
    data: Optional[SeqeuenceTrackData] = None
    plugin_url: ClassVar[str] = "https://unpkg.com/higlass-sequence/dist/higlass-sequence.js"
                
track = SequenceTrack(**{
 "uid": "seq_fasta_example",
 "type": "horizontal-sequence",
 "data": {
   "type": "fasta",
   "fastaUrl": "https://aveit.s3.amazonaws.com/higlass/data/sequence/hg38.fa",
   "faiUrl": "https://aveit.s3.amazonaws.com/higlass/data/sequence/hg38.fa.fai",
   "chromSizesUrl": "https://aveit.s3.amazonaws.com/higlass/data/sequence/hg38.mod.chrom.sizes"
 },
 "width": 568,
 "height": 50
})

track.data # data is a pydantic model
track.data.json()
track.data.dict()<jupyter_output><empty_output><jupyter_text>And the track is faithfully rendered by HiGlass<jupyter_code>hg.view((track, 'center'))<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.12637130801687763, 'fraction_numerical': 0.006329113924050633, 'mean_word_length': 4.424485125858124, 'pattern_counts': {'":': 26, '<': 21, '<?xml version=': 0, '>': 21, 'https://': 7, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '5', 'dir': 'jupyter-structured-clean-dedup', 'id': '580543', 'license': 'MIT', 'n_tokens_mistral': 1600, 'n_tokens_neox': 1522, 'n_words': 393, 'path': 'notebooks/Plugin.ipynb', 'repo_name': 'manzt/hg', 'size': '4740'}