Package: hrepr
hreprhreproutputs HTML/pretty representations for Python objects.✅ Nice, colourful representations of lists, dicts, dataclasses, booleans...✅ Ridiculously extensible and configurable✅ Handles recursive data structures✅ Compatible with Jupyter notebooksI suggest studying the example file to learnhrepr:python examples/exhibit.py > exhibit.html(and then view the HTML file)Also see the Jupyter notebook atexamples/Basics.ipynb, but keep in mind that GitHub can't display it properly because of the injected styles/scripts.InstallpipinstallhreprUsagefromhreprimporthreprobj={'potatoes':[1,2,3],'bananas':{'cantaloups':8}}# Print the HTML representation of objprint(hrepr(obj))# Wrap the representation in <html><body> tags and embed the default# css style files in a standalone page, which is saved to obj.htmlhrepr.page(obj,file="obj.html")In a Jupyter Notebook, you can returnhrepr(obj)from any cell and it will show its representation for you. You can also writedisplay_html(hrepr(obj)).Custom representationsA custom representation for an object can be defined using the following three methods (it is not necessary to define all of them, only those that are relevant to your case):__hrepr__(self, H, hrepr)returns the normal HTML representation.UseH.span["some-class"](some-content, some_attr=some_value)to generate HTML.Usehrepr(self.x)to generate the representation for some subfieldx.hrepr.configcontains any keyword arguments given in the top level call tohrepr. For instance, if you callhrepr(obj, blah=3), thenhrepr.config.blah == 3in all calls to__hrepr__down the line (the default value for all keys isNone).__hrepr_short__(self, H, hrepr)returns ashortrepresentation, ideally of a constant size.The output of this method is used when we hit max depth, or for repeated references.Only include bare minimum information. Short means short.__hrepr_resources__(cls, H)is aclassmethodthat returns resources common to all instances of the class (typically a stylesheet or a script).When generating a page, the resources will go in<head>.You can return a list of resources.No dependency onhrepris necessary.For example:classPerson:def__init__(self,name,age,job):self.name=nameself.age=ageself.job=job@classmethoddef__hrepr_resources__(cls,H):# Note: you might need to add "!important" next to some rules if# they conflict with defaults from hrepr's own CSS.returnH.style(""".person {background: magenta !important;border-color: magenta !important;}.person-short { font-weight: bold; color: green; }""")def__hrepr__(self,H,hrepr):# hrepr.make.instance is a helper to show a table with a header that# describes some kind of objectreturnhrepr.make.instance(title=self.name,fields=[["age",self.age],["job",self.job]],delimiter=" ↦ ",type="person",)def__hrepr_short__(self,H,hrepr):returnH.span["person-short"](self.name)Referenceshreprcan handle circular references. Furthermore, if an object is found at several places in a structure, only the first occurrence will be printed in full, and any other will be a numeric reference mapped to the short representation for the object. It looks like this:Theshortrefsandnorefsconfiguration keys control the representation of references:norefsis ignored when there are circular references.HTML generationGenerate HTML using theHparameter to__hrepr__, or import it and use it directly:fromhreprimportHhtml=H.span["bear"]("Only ",H.b("YOU")," can prevent forest fires!",style="color: brown;")print(html)# <span class="bear" style="color: brown;">Only <b>YOU</b> can prevent forest fires!</span>Hcan be built incrementally: if you have an element, you can call it to add children, index it to add classes, and so on. For instance:fromhreprimportHhtml=H.span()html=html("Only ")html=html(style="color: brown;")["bear"]html=html(H.b("YOU")," can prevent forest fires!")print(html)# <span class="bear" style="color: brown;">Only <b>YOU</b> can prevent forest fires!</span>This can be handy if you want to tweak generated HTML a little. For example,hrepr(obj)["fox"]will tack on the classfoxto the representation of the object.Helpershrepr.make.instance(title, fields, delimiter=None, type=None): formats the fields like a dataclass, with title on top.hrepr.make.bracketed(body, start, end, type=None): formats the body with the specified start/end bracket.Constructed elementsTo make it a bit easier to include and use JavaScript libraries, you can use the special__constructorattribute.For example, you can load Plotly and create a plot like this:defPlot(data):returnH.div(__constructor={"script":"https://cdn.plot.ly/plotly-latest.min.js","symbol":"Plotly.newPlot","options":[{"x":list(range(len(data))),"y":list(data)}],})print(Plot([math.sin(x/10)forxinrange(100)]))The above will:Load the specified script.Get thePlotly.newPlotfunction in the global namespace.Call it with thedivelement as the first argument, and theoptionsas the second argument.It will look like this:ModulesAnother example, this time using ESM (modules):node=H.div(style="width:500px;height:500px;border:1px solid black;",__constructor={"module":"https://cdnjs.cloudflare.com/ajax/libs/cytoscape/3.23.0/cytoscape.esm.min.js","arguments":{"container":H.self(),"elements":[{"data":{"id":"A"}},{"data":{"id":"B"}},{"data":{"id":"C"}},{"data":{"source":"A","target":"B"}},{"data":{"source":"B","target":"C"}},{"data":{"source":"C","target":"A"}},],"style":cystyle,"layout":{"name":"cose"},},},)print(node)The above will:Import the specified module.Call the module's default export witharguments.Note the use ofH.self()to refer to the targetdivin the arguments.If you wish to use a non-default export, set thesymbolkey in the__constructorattribute to the name of the export you want.Customize hreprMixinsIf you want toreallycustomize hrepr, you can use mixins. They look like a bit of black magic, but they're simple enough:# ovld is one of the dependencies of hreprfromovldimportovld,extend_super,has_attribute,OvldMCfromhreprimporthreprclassMyMixin(metaclass=OvldMC):# Change the representation of integers@extend_superdefhrepr_resources(self,cls:int):# Note: in hrepr_resources, cls is the int type, not an integerreturnself.H.style(".my-integer { color: fuchsia; }")@extend_superdefhrepr(self,n:int):returnself.H.span["my-integer"]("The number ",str(n))# Specially handle any object with a "quack" methoddefhrepr(self,duck:has_attribute("quack")):returnself.H.span("🦆")The annotation for a rule can either be a type,ovld.has_attribute, or pretty much any function wrapped with theovld.metadecorator, as long as the function operates on classes. See the documentation forovldfor more information.And yes, you can definehreprmultiple times inside the class, as long as they have distinct annotations and you inherit fromHrepr. You can also definehrepr_shortorhrepr_resourcesthe same way.Postprocessorshreprcan be given a postprocessor that is called on the representation of any object. You can use this to do things like highlighting specific objects:fromhreprimportHstyle=H.style(".highlight { border: 3px solid red !important; }")defhighlight(x):defpostprocess(element,obj,hrepr):ifobj==x:# Adds the "highlight" class and attaches a stylereturnelement["highlight"].fill(resources=style)else:returnelementreturnpostprocesshrepr([1,2,[3,4,2]],postprocess=highlight(2))hrepr variantsTo put this all together, you can create avariantofhrepr:hrepr2=hrepr.variant(mixins=MyMixin,postprocess=highlight(2))hrepr2([1,2,3])# Will use the mixins and postprocessorConfigure the hrepr function itselfAlternatively, you can configure the mainhrepr:hrepr.configure(mixins=MyMixin,postprocess=highlight(2))But keep in mind that unlike the variant, the above will modifyhreprfor everything else as well.