DetailsJSON

Collapsible JSON/dict viewer component for notebooks

Derivation

app, brt, rt = get_app()#show_logger=True, summary=True)
<style>
    me ul { list-style-type:none; list-style-position: outside; padding-inline-start: 22px; margin: 0; }
</style>
<details open>
<summary>Apollo astronauts</summary>
<ul>
  <li><span>1</span>: Neil Armstrong</li>
  <li><span>2</span>: Alan Bean</li>
  <li><details>
<summary>Apollo 11</summary>
<ul>
  <li><span>1</span>: Neil Armstrong</li>
  <li><span>2</span>: Alan Bean</li>
  <li><div><span>3</span>: Buzz Aldrin</div></li>
  <li><span>4</span>: Edgar Mitchell</li>
  <li><span>5</span>: Alan Shepard</li>
</ul></li>
  <li><span>4</span>: Edgar Mitchell</li>
  <li><span>5</span>: Alan Shepard</li>
</ul>

</details>
Apollo astronauts
  • 1: Neil Armstrong
  • 2: Alan Bean
  • Apollo 11
    • 1: Neil Armstrong
    • 2: Alan Bean
    • 3: Buzz Aldrin
    • 4: Edgar Mitchell
    • 5: Alan Shepard
  • 4: Edgar Mitchell
  • 5: Alan Shepard
def Val(v): 
    c = (
        'null' if v is None else 
        'true' if v is True else 
        'false' if v is False else 
        'string' if isinstance(v, str) else 
        'number' if isinstance(v, (int, float)) else 
        '')
    return Span(shorten(v, 'r', 140) if v is not None else 'None', cls=f"v {c}")
def NameVal(k, v): return Span(Span(k, cls='n'), ': ', Val(v))


class DetailsJSON(dict):
    def __init__(self, *args, summary:str='', open:bool=True, openall:bool=False, skip:Sequence[str]=(), **kwargs):
        super().__init__(*args, **kwargs)
        self.summary, self.open, self.openall, self.skip = str(summary), open, openall, skip
    def __ft__(self, d:Mapping|None=None, summary:str|None=None, lvl:int=0, open:bool=False):
        if d is None: d = self; summary = self.summary or 'summary'; open=self.open
        open = self.openall or open
        return (
            Style(self._css_) if lvl == 0 else (), 
            Details(open=open)(
                Summary(summary, _n),
                Ul()(*(
                    Li(NameVal(k, v)) if k in self.skip else
                    self.__ft__(v, k, lvl+1) if isinstance(v, Mapping) else
                    self.__ft__(dict(list(zip(range(len(v)), v))), k, lvl+1) if is_listy(v) else
                    Li(NameVal(k, v)) 
                    for k,v in d.items()))))
    _css_ = (
        'details ul { list-style-type:none; list-style-position: outside; padding-inline-start: 22px; margin: 0; } '
        '''details .string { color: #24837b; } details .string::before { content: "'"; } details .string::after { content: "'"; } '''
        'details .number { color: #ad8301; } '
        'details .true { color: blue; } '
        'details .false { color: red; } '
        'details .null { color: gray; } '
        'span.n { color: darkgrey; } '
    )


# class DetailsJSON(dict):
#     def __init__(self, *args, summary:str='', open:bool=False, lvl:int=0, **kwargs):
#         super().__init__(*args, **kwargs)
#         self.summary, self.open, self.lvl = summary, open, lvl
#     def __ft__(self):
#         return (
#             Style(self._css_) if self.lvl == 0 else (), 
#             Details(open=self.open)(
#                 Summary(self.summary or 'summary', _n),
#                 Ul()(*(
#                     Li(NameVal(k, v)) if not isinstance(v, Mapping) else 
#                     DetailsJSON(v, summary=k, lvl=self.lvl+1) 
#                     for k,v in self.items()))))
#     _css_ = 'me ul { list-style-type:none; list-style-position: outside; padding-inline-start: 22px; margin: 0; } '


dtl = DetailsJSON({
    '1': 'Neil Armstrong',
    '2': 'Alan Bean',
    '3': 'Buzz Aldrin',
    'letters': {
        'a': 1, 
        'b': 2
        },
    '5': 'Edgar Mitchell',
    '6': 'Alan Shepard'
}, summary='Apollo astronauts', open=True)

test_eq(val_at(dtl, '5'), 'Edgar Mitchell')
test_eq(val_at(dtl, 'letters.a'), 1)
print(to_xml(dtl))
show(dtl)
<style>details ul { list-style-type:none; list-style-position: outside; padding-inline-start: 22px; margin: 0; } details .string { color: #24837b; } details .string::before { content: "'"; } details .string::after { content: "'"; } details .number { color: #ad8301; } details .true { color: blue; } details .false { color: red; } details .null { color: gray; } span.n { color: darkgrey; } </style>
<details open><summary>Apollo astronauts
</summary>  <ul>
    <li>
<span><span class="n">1</span>: <span class="v string">Neil Armstrong</span></span>    </li>
    <li>
<span><span class="n">2</span>: <span class="v string">Alan Bean</span></span>    </li>
    <li>
<span><span class="n">3</span>: <span class="v string">Buzz Aldrin</span></span>    </li>
<details><summary>letters
</summary>      <ul>
        <li>
<span><span class="n">a</span>: <span class="v number">1</span></span>        </li>
        <li>
<span><span class="n">b</span>: <span class="v number">2</span></span>        </li>
      </ul>
</details>    <li>
<span><span class="n">5</span>: <span class="v string">Edgar Mitchell</span></span>    </li>
    <li>
<span><span class="n">6</span>: <span class="v string">Alan Shepard</span></span>    </li>
  </ul>
</details>
Apollo astronauts
  • 1: Neil Armstrong
  • 2: Alan Bean
  • 3: Buzz Aldrin
  • letters
    • a: 1
    • b: 2
  • 5: Edgar Mitchell
  • 6: Alan Shepard
def walk(m:Mapping, p:str=''):
    for k,v in m.items():
        if isinstance(v, Mapping): yield from walk(v, f"{p}.{k}" if p else k)
        else: yield f"{p}.{k}" if p else k,v 

list(walk(dtl))
[('1', 'Neil Armstrong'),
 ('2', 'Alan Bean'),
 ('3', 'Buzz Aldrin'),
 ('letters.a', 1),
 ('letters.b', 2),
 ('5', 'Edgar Mitchell'),
 ('6', 'Alan Shepard')]
def walk(mapping: Mapping, prefix: str=''):
    stack = [(prefix, list(mapping.items()))]
    while stack:
        p, items = stack[-1]
        if not items: stack.pop(); continue
        k, v = items.pop(0)
        if isinstance(v, Mapping): stack.append((f"{p}.{k}" if p else k, list(v.items())))
        else: yield f"{p}.{k}" if p else k, v

# Test
list(walk(dtl))
[('1', 'Neil Armstrong'),
 ('2', 'Alan Bean'),
 ('3', 'Buzz Aldrin'),
 ('letters.a', 1),
 ('letters.b', 2),
 ('5', 'Edgar Mitchell'),
 ('6', 'Alan Shepard')]
def update(d, other:Mapping|None=None, **kwargs):
    if isinstance(d, dict): d.update(other or {}, **kwargs)
    else:
        for k,v in {**(other or {}), **kwargs}.items(): 
            try: setattr(d, k, v)
            except AttributeError: pass
    return d
d = {'a': 1, 'b':2}
test_eq(update(d, c=3), {'a': 1, 'b':2, 'c':3})
test_eq(update(d, {'b': 22}), {'a': 1, 'b':22, 'c':3})

DetailsJSON


source

DetailsJSON

 DetailsJSON (o:Mapping[str,Any], summary:str='',
              open:Union[bool,Literal['all']]=True)

Base class for objects that provide routes via an APIRouter


source

NameVal

 NameVal (k, v)

source

Val

 Val (v)
brt.app.routes.clear()
bridge_cfg.auto_show = False

dtl = DetailsJSON({
    '1': 'Neil Armstrong', '2': 'Alan Bean', '3': 'Buzz Aldrin',
    'letters': { 'a': 1, 'b': True, 'c': {'d': None} },
    '5': 'Edgar Mitchell', '6': 'Alan Shepard'
}, summary='Apollo astronauts')

brt.mount(dtl, '/details', 'details', show=False)

test_eq(dtl.ar.name(), 'DetailsJSON:details')
test_eq(app.url_path_for('DetailsJSON:details:get', dp=''), '/DetailsJSON/details/')
test_eq(app.url_path_for('DetailsJSON:details:get', dp='letters'), '/DetailsJSON/details/letters')

dtl()
<details open hx-swap="outerHTML">
  <style>me ul { list-style-type:none; list-style-position: outside; padding-inline-start: 22px; margin: 0; } me .string { color: #24837b; } me .string::before { content: "'"; } me .string::after { content: "'"; } me .number { color: #ad8301; } me .true { color: blue; } me .false { color: red; } me .null { color: gray; } me .n { color: darkgrey; } </style>
<summary>Apollo astronauts</summary>
  <ul>
    <li>
<span><span class="n">1</span>: <span class="v string">Neil Armstrong</span></span>    </li>
    <li>
<span><span class="n">2</span>: <span class="v string">Alan Bean</span></span>    </li>
    <li>
<span><span class="n">3</span>: <span class="v string">Buzz Aldrin</span></span>    </li>
    <li>
<details hx-get="/DetailsJSON/details/letters">
<summary>letters</summary>
</details>    </li>
    <li>
<span><span class="n">5</span>: <span class="v string">Edgar Mitchell</span></span>    </li>
    <li>
<span><span class="n">6</span>: <span class="v string">Alan Shepard</span></span>    </li>
  </ul>
</details>
dtl('letters')
<details open><summary>letters</summary>
  <ul>
    <li>
<span><span class="n">a</span>: <span class="v number">1</span></span>    </li>
    <li>
<span><span class="n">b</span>: <span class="v true">True</span></span>    </li>
    <li>
<details hx-get="/DetailsJSON/details/letters/c">
<summary>c</summary>
</details>    </li>
  </ul>
</details>
dtl('letters/c')
<details open><summary>c</summary>
  <ul>
    <li>
<span><span class="n">d</span>: <span class="v null">None</span></span>    </li>
  </ul>
</details>
print(brt.cli.get('/DetailsJSON/details/letters', headers={'hx-request': '1'}).text)
<details open><summary>letters</summary>
   <ul>
     <li>
<span><span class="n">a</span>: <span class="v number">1</span></span>     </li>
     <li>
<span><span class="n">b</span>: <span class="v true">True</span></span>     </li>
     <li>
<details hx-get="/DetailsJSON/details/letters/c">
<summary>c</summary>
</details>     </li>
   </ul>
</details>
print(brt.cli.get('/DetailsJSON/details/letters/c', headers={'hx-request': '1'}).text)
<details open><summary>c</summary>
   <ul>
     <li>
<span><span class="n">d</span>: <span class="v null">None</span></span>     </li>
   </ul>
</details>
bridge_cfg.auto_show = True
dtl()
Apollo astronauts
  • 1: Neil Armstrong
  • 2: Alan Bean
  • 3: Buzz Aldrin
  • letters
  • 5: Edgar Mitchell
  • 6: Alan Shepard
brt('/DetailsJSON/details/');
Apollo astronauts
  • 1: Neil Armstrong
  • 2: Alan Bean
  • 3: Buzz Aldrin
  • letters
  • 5: Edgar Mitchell
  • 6: Alan Shepard
# bridge_cfg.auto_mount = True

apollo_astronauts = json.load(Path('static/apollo_astronauts.json').open())

(astro := DetailsJSON(apollo_astronauts, summary='Apollo astronauts'))
Apollo astronauts
  • Apollo 7
  • Apollo 8
  • Apollo 9
  • Apollo 10
  • Apollo 11
  • Apollo 12
  • Apollo 13
  • Apollo 14
  • Apollo 15
  • Apollo 16
  • Apollo 17
request = {
    'headers': {
        'HX-Request': 'true',
        'HX-Current-URL': 'vscode-webview://1ql27...enderer'
    },
    'headerNames': {
        'hx-request': 'HX-Request',
        'hx-current-url': 'HX-Current-URL'
    },
    'status': 0,
    'method': 'GET',
    'url': '/DetailsJSON_5096628128/Apollo 11/Buzz Aldrin',
    'async': True,
    'timeout': 0,
    'withCredentials': False,
    'body': None,
    'req_id': '68ffadb0-958d-4346-b314-d9d62ca247d7'
}

response = {
    'headers': {
        'content-length': '441',
        'content-type': 'text/html; charset=utf-8',
        'last-modified': 'Fri, 15 Nov 2024 16:22:15 GMT',
        'cache-control': 'no-store, no-cache, must-revalidate'
    },
    'status': 200,
    'statusText': 'OK',
    'data': '<details open><summary>Buzz Aldrin</summary>\n   <ul>\n     '
'<li>Pilot on Gemini 12 and Lunar Module pilot on Apollo 11.</li>\n     '
'<li>Aldrin was the second person to walk on the moon.</li>\n     <li>The maiden '
'name of Aldrin&#x27;s mother was &quot;Moon.&quot;</li>\n     <li>While Neil was '
'the first human to step onto the moon, I&#x27;m the first alien from another '
'world to enter a spacecraft that was going to Earth.</li>\n   '
'</ul>\n</details>',
    'xml': None,
    'finalUrl': 'http://nb/DetailsJSON_5096628128/Apollo%2011/Buzz%20Aldrin',
    'req_id': '68ffadb0-958d-4346-b314-d9d62ca247d7'
}
(req := DetailsJSON(request, summary='request', open='all'))
request
  • headers
    • HX-Request: true
    • HX-Current-URL: vscode-webview://1ql27...enderer
  • headerNames
    • hx-request: HX-Request
    • hx-current-url: HX-Current-URL
  • status: 0
  • method: GET
  • url: /DetailsJSON_5096628128/Apollo 11/Buzz Aldrin
  • async: True
  • timeout: 0
  • withCredentials: False
  • body: None
  • req_id: 68ffadb0-958d-4346-b314-d9d62ca247d7
(resp := DetailsJSON(response, summary='response'))
response
  • headers
  • status: 200
  • statusText: OK
  • data: <details open><summary>Buzz Aldrin</summary> <ul> <li>Pilot on Gemini 12 and Lunar Module pilot on Apollo 11.</li> <li>Aldrin w…
  • xml: None
  • finalUrl: http://nb/DetailsJSON_5096628128/Apollo%2011/Buzz%20Aldrin
  • req_id: 68ffadb0-958d-4346-b314-d9d62ca247d7