Inspection

Let's remake the Person class in the tour:


In [1]:
from descr import boxy_notebook, descr, HTMLRuleBuilder as RB
from descr.util import Object
pr = boxy_notebook(always_setup = True)

class Person(object):
    def __init__(self, name, age, occupation):
        self.name, self.age, self.occupation = name, age, occupation
    def __descr__(self, recurse):
        return recurse(Object("Person", self.name, age = self.age, occupation = self.occupation))
    
peter = Person("Peter", 20, "baker")
pr(peter)


Peterage20occupationbaker

Now, let's investigate what exactly it is that we are dealing with. The function that calls __descr__ and will give itself as the recurse argument is, by default, the descr function (see import on top).


In [2]:
descr(peter)


Out[2]:
[frozenset(['@Person', '+Peter', 'object']),
 [set(['field', '+age']), frozenset(['scalar', '@int']), '20'],
 [set(['+occupation', 'field']), frozenset(['@str', 'scalar']), 'baker']]

So it's really just a list of lists or tuples, with sets of classes. In a nutshell:

  • str, int and other basic types are leaves
  • Any iterable that's not a set is a node with other nodes and leaves as children
  • set (or frozenset), when it is a child of a node, is a set of classes for that node. If more than one is encountered, the node's classes is their union.

And about the classes you can make the following observations:

  • @foo is the class given to objects of class Foo
  • +bar is the class given to the label "bar"
  • Non-prefixed classes each have their own special meaning

Now, you could pretty-print that:


In [3]:
pr(descr(peter))


@Person+Peterobjectfield+agescalar@int20+occupationfield@strscalarbaker

However, that's confusing, because the differences between sets, lists and tuples only show up on hover even though the difference is properly crucial, and if there are generators, they will not be expanded (even though they would be if they were describing anything else than themselves).

Thankfully, you can use Description(descr(obj)) or Quote(obj) to get something better:


In [4]:
from descr.util import Quote, Description
pr(Quote([1, 2.3, 4+5j]))
pr(Quote(dict(a = 1, b = 2)))
pr(Quote(peter)) # alternatively: pr(Description(descr(peter)))


@listsequence@intscalar1@floatscalar2.3@complexscalar(4+5j)
@dictsequenceassoc@strscalara@intscalar1assoc@strscalarb@intscalar2
+Peter@Personobject+agefield@intscalar20+occupationfield@strscalarbaker

Still, though, many processing rules that are applied to such descriptions add classes to nodes, and some even rearrange the children. Quote gives you the description prior to the application of any rules, but you might want to see the result at various stages of the transformation. For instance, @list, @tuple and other sequences get the sequence class. @str, @int, etc. get the scalar class.

For instance, the CSS rules understandably apply to the final HTML that's generated. You can get the raw string using pr.translate(descr(obj)), which is certainly informative (it will show you the CSS, for one). You can also use the inspect rule:


In [5]:
pr(pr.translate(descr(peter)))
rules = RB().inspect("*")
pr(peter, rules = rules)
rules = RB().inspect(".{+age}")
pr(peter, rules = rules)


<style type="text/css"> .pydescr0 .\@str, .hl, .hl1, .hl2, .hl3, .hlE { } .pydescr0 .sequence { border: 2px solid #eee; margin: 3px; padding: 3px; } .pydescr0 .object { } .pydescr0 .fieldlist > .field { } .pydescr0 .\@traceback, .\@frame { } .pydescr0 .\@frame { } .pydescr0 .location { } .pydescr0 .lineno { border-right: 4px solid #00f; display: inline-block; margin-right: 10px; padding-right: 5px; text-align: right; } .pydescr0 { display: block; } .pydescr0 span { display: inline-block; font-family: monospace; vertical-align: middle; } .pydescr0 .text * { display: inline; } .pydescr0 .pre * { display: inline; white-space: pre; } .pydescr0 .scalar { background-color: #eee; margin: 3px; max-height: 300px; overflow: auto; padding: 3px; white-space: pre; } .pydescr0 .empty.\@str::before { content: "\"\""; } .pydescr0 .empty.sequence::before { content: "\2205"; } .pydescr0 .stack { display: -webkit-inline-box; display: -moz-inline-box; -moz-box-align: middle; -moz-box-orient: horizontal; -webkit-box-align: middle; -webkit-box-orient: horizontal; box-align: middle; box-orient: horizontal; display: inline-box; margin: 3px; } .pydescr0 .vstack { -moz-box-orient: vertical; -webkit-box-orient: vertical; box-orient: vertical; } .pydescr0 .hstack { -moz-box-orient: horizontal; -webkit-box-orient: horizontal; box-orient: horizontal; } .pydescr0 .stack > span { display: block; margin: 0px; } .pydescr0 .assoc { } .pydescr0 .\@traceback { border: 1px dashed #888; } .pydescr0 .\@traceback > * { display: block; } .pydescr0 .source_excerpt { width: 100%; } .pydescr0 .source_excerpt > * { display: block; } .pydescr0 .source_header { background-color: #eee; width: 98%; } .pydescr0 .source_header > .path, .source_header > .source_loc { display: block; float: right; } .pydescr0 .path, .source_loc, .\@frame > .\+fname { } .pydescr0 .\+fname + .path::before { color: #666; content: "in "; } .pydescr0 .path + .source_loc::before { color: #666; content: "@"; font-weight: normal; } .pydescr0 .source\_code { } .pydescr0 .quote.class_set > * { background-color: #eee; color: blue; margin-left: 3px; margin-right: 3px; } .pydescr0 .quote.class_set { border: 2px solid blue; margin: 3px; padding: 3px; } .pydescr0 .quote.description { border: 2px solid #aaa; margin: 3px; padding: 3px; } .pydescr0 .table { border: 1px solid #000; border-collapse: collapse; display: table; } .pydescr0 .table > * { display: table-row; } .pydescr0 .table > * > * { display: table-cell; padding: 0px 10px 0px 10px; } .pydescr0 .table > .header { border-bottom: 2px solid #000; font-weight: bold; } .pydescr0 .\@True { color: #080; } .pydescr0 .\@False { color: #f00; } .pydescr0 .\@None { color: #555; } .pydescr0 .\@int { color: #00a; } .pydescr0 .\@float { color: #00a; } .pydescr0 .\@complex { color: #00a; } .pydescr0 .\@str { color: #a00; } .pydescr0 .empty { color: #888; } .pydescr0 .empty.sequence { border: 2px solid #fff; } .pydescr0 .\@tuple:hover { border: 2px solid #bbb; } .pydescr0 .\@list:hover { border: 2px solid #f88; } .pydescr0 .\@dict:hover { border: 2px solid #6a6; } .pydescr0 .\@set:hover { border: 2px solid #88f; } .pydescr0 .fieldlist { border-bottom: 2px solid #00f; } .pydescr0 .assoc:hover > .assoc_separator { border: 2px solid #000; } .pydescr0 .assoc\_separator { border: 2px solid #888; } .pydescr0 .objectlabel { color: #00f; font-weight: bold; } .pydescr0 .objectlabel + .assoc\_separator { border: 2px solid #00f; } .pydescr0 .fieldlabel { color: #a00; } .pydescr0 .traceback_separator { border: 2px solid #888; } .pydescr0 .\@Exception .objectlabel { color: #f00; text-align: left; } .pydescr0 .\@Exception .objectlabel + .assoc\_separator { border: 2px solid #800; } .pydescr0 .\@Exception .fieldlist { border-bottom: 4px solid #800; } .pydescr0 .exception_message { display: block; } .pydescr0 .quote.description:hover { border: 2px solid #000; } .pydescr0 .\@HTMLNode > .\+classes { background-color: #eee; } .pydescr0 .\@HTMLNode > .\+classes > * { color: #00f; padding: 3px; } .pydescr0 .hl { font-weight: bold; } .pydescr0 .hl1 { background-color: #eef; color: #00f; font-weight: bold; } .pydescr0 .hl2 { background-color: #efe; color: #0a0; font-weight: bold; } .pydescr0 .hl3 { background-color: #efc; color: #a60; font-weight: bold; } .pydescr0 .hlE { background-color: #fee; color: #f00; font-weight: bold; } .pydescr0 .bold { font-weight: bold; } .pydescr0 .black { color: #000; } .pydescr0 .red { color: #f88; } .pydescr0 .green { color: #8f8; } .pydescr0 .yellow { color: #ff8; } .pydescr0 .blue { color: #88f; } .pydescr0 .magenta { color: #f8f; } .pydescr0 .cyan { color: #8ff; } .pydescr0 .white { color: #fff; } .pydescr0 .par { } .pydescr0 .line { } .pydescr0 .raw { } </style><span class="pydescr0"><span class="@Person +Peter assoc object:done object vstack stack"><span class="scalar objectlabel"><span class="">Peter</span></span><span class="assoc_separator"></span><span class="fieldlist sequence"><span class="vstack assoc stack"><span class="scalar fieldlabel"><span class="">age</span></span><span class="assoc_separator"></span><span class="field scalar +age @int"><span class="">20</span></span></span><span class="vstack assoc stack"><span class="scalar fieldlabel"><span class="">occupation</span></span><span class="assoc_separator"></span><span class="+occupation scalar field @str"><span class="">baker</span></span></span></span></span></span>
+Peter@Personassocobjectobject:donestackvstackobjectlabelscalarPeterassoc_separatorfieldlistsequenceassocstackvstackfieldlabelscalarageassoc_separator+age@intfieldscalar20assocstackvstackfieldlabelscalaroccupationassoc_separator+occupation@strfieldscalarbaker
Peterage+age@intfieldscalar20occupationbaker

You can see how that is structured by inspecting descr.html.HTMLNode objects.


In [6]:
from descr.html import HTMLNode
node = HTMLNode({"class1", "class2"}, ["child1", "child2"])
pr(node)
pr(Quote(node))
rules = RB().inspect("*")
pr(node, rules = rules)


class1class2child1child2
@HTMLNodeassoc+classesfieldclass1class2+childrenfield@strscalarchild1@strscalarchild2
@HTMLNodeassocstackvstack+classesfieldclass1class2assoc_separator+childrenfield@strscalarchild1@strscalarchild2

This should suffice to tell you what kind of rules to make to manipulate that output (clearing out uninteresting classes, highlighting interesting ones, etc.) For instance:


In [7]:
hide = "scalar sequence stack vstack assoc_separator".split()
rules = RB().inspect("*").rearrange(".{@HTMLNode} > .{+classes}", lambda classes, children: [child for child in children if child not in hide] or ['---'])
pr(peter, rules = rules)


+Peter@Personassocobjectobject:doneobjectlabelPeter---fieldlistassocfieldlabelage---+age@intfield20assocfieldlabeloccupation---+occupation@strfieldbaker

In [7]: