mirror of
https://github.com/balkian/balkian.github.com.git
synced 2025-04-19 19:29:04 +00:00
70 lines
26 KiB
HTML
70 lines
26 KiB
HTML
<!doctype html><html lang=en-us dir=ltr><head><meta charset=utf-8><meta name=viewport content='width=device-width,initial-scale=1'><meta name=description content="In the RDF world, data is expressed as a collection of triples. These triples can contain IRIs that may or may not be accessible or valid. And the use of these IRIs may or may not adhere to a vocabulary. Checking the validity of the IRIs and the semantics of the triples is an additional step.\nThe rdflib way rdflib only models IRIs, values and namespaces. Developers need to be cognisant of the URIs they are using, and the vocabularies being used. Prior to version 2.0, senpy followed a very similar model. It had a base class to represent a generic node. Each instance then gets its own automatically generated id, and will act like a normal dictionary, whose keys and values will be serialized as a JSON-LD dictionary. Multiple subclasses were also included to model specific types of node, mostly to provide convenience methods for the given subtype. Here is an example of a subclass, Entity.\n"><title>Bridging RDF, JSON-LD and Dataclasses</title>
|
|
<link rel=canonical href=https://balkian.com/p/bridging-rdf-json-ld-and-dataclasses/><link rel=stylesheet href=/scss/style.min.314a81d1fef50606da5df138ce819c12f9ed0c4f2487c1964bccb4c3cc737879.css><meta property='og:title' content="Bridging RDF, JSON-LD and Dataclasses"><meta property='og:description' content="In the RDF world, data is expressed as a collection of triples. These triples can contain IRIs that may or may not be accessible or valid. And the use of these IRIs may or may not adhere to a vocabulary. Checking the validity of the IRIs and the semantics of the triples is an additional step.\nThe rdflib way rdflib only models IRIs, values and namespaces. Developers need to be cognisant of the URIs they are using, and the vocabularies being used. Prior to version 2.0, senpy followed a very similar model. It had a base class to represent a generic node. Each instance then gets its own automatically generated id, and will act like a normal dictionary, whose keys and values will be serialized as a JSON-LD dictionary. Multiple subclasses were also included to model specific types of node, mostly to provide convenience methods for the given subtype. Here is an example of a subclass, Entity.\n"><meta property='og:url' content='https://balkian.com/p/bridging-rdf-json-ld-and-dataclasses/'><meta property='og:site_name' content='J. Fernando Sánchez'><meta property='og:type' content='article'><meta property='article:section' content='Post'><meta property='article:tag' content='rdf'><meta property='article:tag' content='json-ld'><meta property='article:tag' content='pydantic'><meta property='article:tag' content='python'><meta property='article:published_time' content='2025-02-26T23:22:59+01:00'><meta property='article:modified_time' content='2025-02-26T23:22:59+01:00'><meta name=twitter:title content="Bridging RDF, JSON-LD and Dataclasses"><meta name=twitter:description content="In the RDF world, data is expressed as a collection of triples. These triples can contain IRIs that may or may not be accessible or valid. And the use of these IRIs may or may not adhere to a vocabulary. Checking the validity of the IRIs and the semantics of the triples is an additional step.\nThe rdflib way rdflib only models IRIs, values and namespaces. Developers need to be cognisant of the URIs they are using, and the vocabularies being used. Prior to version 2.0, senpy followed a very similar model. It had a base class to represent a generic node. Each instance then gets its own automatically generated id, and will act like a normal dictionary, whose keys and values will be serialized as a JSON-LD dictionary. Multiple subclasses were also included to model specific types of node, mostly to provide convenience methods for the given subtype. Here is an example of a subclass, Entity.\n"><link rel="shortcut icon" href=/img/favicon.ico></head><body class=article-page><script>(function(){const e="StackColorScheme";localStorage.getItem(e)||localStorage.setItem(e,"auto")})()</script><script>(function(){const t="StackColorScheme",e=localStorage.getItem(t),n=window.matchMedia("(prefers-color-scheme: dark)").matches===!0;e=="dark"||e==="auto"&&n?document.documentElement.dataset.scheme="dark":document.documentElement.dataset.scheme="light"})()</script><div class="container main-container flex on-phone--column extended"><aside class="sidebar left-sidebar sticky"><button class="hamburger hamburger--spin" type=button id=toggle-menu aria-label="Toggle Menu">
|
|
<span class=hamburger-box><span class=hamburger-inner></span></span></button><header><figure class=site-avatar><a href=/><img src=/img/me_hu_57f477f2a0e68f7e.png width=300 height=300 class=site-logo loading=lazy alt=Avatar>
|
|
</a><span class=emoji>💭</span></figure><div class=site-meta><h1 class=site-name><a href=/>J. Fernando Sánchez</a></h1><h2 class=site-description>My ramblings and reflections</h2></div></header><ol class=menu-social><li><a href=https://github.com/CaiJimmy/hugo-theme-stack target=_blank title=GitHub rel=me><svg class="icon icon-tabler icon-tabler-brand-github" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentcolor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M9 19c-4.3 1.4-4.3-2.5-6-3m12 5v-3.5c0-1 .1-1.4-.5-2 2.8-.3 5.5-1.4 5.5-6a4.6 4.6.0 00-1.3-3.2 4.2 4.2.0 00-.1-3.2s-1.1-.3-3.5 1.3a12.3 12.3.0 00-6.2.0C6.5 2.8 5.4 3.1 5.4 3.1a4.2 4.2.0 00-.1 3.2A4.6 4.6.0 004 9.5c0 4.6 2.7 5.7 5.5 6-.6.6-.6 1.2-.5 2V21"/></svg></a></li><li><a href=https://git.sinpapel.es/balkian target=_blank title=gitea rel=me><svg viewBox="0 0 640 640" width="32" height="32"><path d="m395.9 484.2-126.9-61c-12.5-6-17.9-21.2-11.8-33.8l61-126.9c6-12.5 21.2-17.9 33.8-11.8 17.2 8.3 27.1 13 27.1 13l-.1-109.2 16.7-.1.1 117.1s57.4 24.2 83.1 40.1c3.7 2.3 10.2 6.8 12.9 14.4 2.1 6.1 2 13.1-1 19.3l-61 126.9c-6.2 12.7-21.4 18.1-33.9 12" style="fill:#fff"/><path d="M622.7 149.8c-4.1-4.1-9.6-4-9.6-4s-117.2 6.6-177.9 8c-13.3.3-26.5.6-39.6.7v117.2c-5.5-2.6-11.1-5.3-16.6-7.9.0-36.4-.1-109.2-.1-109.2-29 .4-89.2-2.2-89.2-2.2s-141.4-7.1-156.8-8.5c-9.8-.6-22.5-2.1-39 1.5-8.7 1.8-33.5 7.4-53.8 26.9C-4.9 212.4 6.6 276.2 8 285.8c1.7 11.7 6.9 44.2 31.7 72.5 45.8 56.1 144.4 54.8 144.4 54.8s12.1 28.9 30.6 55.5c25 33.1 50.7 58.9 75.7 62 63 0 188.9-.1 188.9-.1s12 .1 28.3-10.3c14-8.5 26.5-23.4 26.5-23.4S547 483 565 451.5c5.5-9.7 10.1-19.1 14.1-28 0 0 55.2-117.1 55.2-231.1-1.1-34.5-9.6-40.6-11.6-42.6M125.6 353.9c-25.9-8.5-36.9-18.7-36.9-18.7S69.6 321.8 60 295.4c-16.5-44.2-1.4-71.2-1.4-71.2s8.4-22.5 38.5-30c13.8-3.7 31-3.1 31-3.1s7.1 59.4 15.7 94.2c7.2 29.2 24.8 77.7 24.8 77.7s-26.1-3.1-43-9.1m300.3 107.6s-6.1 14.5-19.6 15.4c-5.8.4-10.3-1.2-10.3-1.2s-.3-.1-5.3-2.1l-112.9-55s-10.9-5.7-12.8-15.6c-2.2-8.1 2.7-18.1 2.7-18.1L322 273s4.8-9.7 12.2-13c.6-.3 2.3-1 4.5-1.5 8.1-2.1 18 2.8 18 2.8L467.4 315s12.6 5.7 15.3 16.2c1.9 7.4-.5 14-1.8 17.2-6.3 15.4-55 113.1-55 113.1" style="fill:#609926"/><path d="M326.8 380.1c-8.2.1-15.4 5.8-17.3 13.8s2 16.3 9.1 20c7.7 4 17.5 1.8 22.7-5.4 5.1-7.1 4.3-16.9-1.8-23.1l24-49.1c1.5.1 3.7.2 6.2-.5 4.1-.9 7.1-3.6 7.1-3.6 4.2 1.8 8.6 3.8 13.2 6.1 4.8 2.4 9.3 4.9 13.4 7.3.9.5 1.8 1.1 2.8 1.9 1.6 1.3 3.4 3.1 4.7 5.5 1.9 5.5-1.9 14.9-1.9 14.9-2.3 7.6-18.4 40.6-18.4 40.6-8.1-.2-15.3 5-17.7 12.5-2.6 8.1 1.1 17.3 8.9 21.3s17.4 1.7 22.5-5.3c5-6.8 4.6-16.3-1.1-22.6 1.9-3.7 3.7-7.4 5.6-11.3 5-10.4 13.5-30.4 13.5-30.4.9-1.7 5.7-10.3 2.7-21.3-2.5-11.4-12.6-16.7-12.6-16.7-12.2-7.9-29.2-15.2-29.2-15.2s0-4.1-1.1-7.1c-1.1-3.1-2.8-5.1-3.9-6.3 4.7-9.7 9.4-19.3 14.1-29-4.1-2-8.1-4-12.2-6.1-4.8 9.8-9.7 19.7-14.5 29.5-6.7-.1-12.9 3.5-16.1 9.4-3.4 6.3-2.7 14.1 1.9 19.8z" style="fill:#609926"/></svg></a></li><li><a href='https://scholar.google.com/citations?user=JLNusZ8AAAAJ&hl=en' target=_blank title="Google scholar" rel=me><svg aria-label="Google Scholar" role="img" viewBox="0 0 512 512"><rect width="512" height="512" rx="15%" fill="#4285f4"/><path fill="#fff" d="M213 111l-107 94h69c5 45 41 64 78 67-7 18-4 27 7 39-43 1-103 26-103 67 4 45 63 54 92 54 38 1 81-19 90-54 4-35-10-54-31-71-23-18-28-28-21-40 15-17 35-27 39-51 2-17-2-28-6-43l45-38-1 16c-3 2-5 6-5 9v103c2 13 22 11 23 0V160c0-3-2-7-5-8v-25l16-16zm58 141c-61 10-87-87-38-99 56-11 83 86 38 99zm-5 73c60 13 61 63 10 78-44 9-82-4-81-30 0-25 35-48 71-48z"/></svg></a></li></ol><ol class=menu id=main-menu><li><a href=/><svg class="icon icon-tabler icon-tabler-home" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentcolor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z"/><polyline points="5 12 3 12 12 3 21 12 19 12"/><path d="M5 12v7a2 2 0 002 2h10a2 2 0 002-2v-7"/><path d="M9 21v-6a2 2 0 012-2h2a2 2 0 012 2v6"/></svg>
|
|
<span>Home</span></a></li><li><a href=/search/><svg class="icon icon-tabler icon-tabler-search" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentcolor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z"/><circle cx="10" cy="10" r="7"/><line x1="21" y1="21" x2="15" y2="15"/></svg>
|
|
<span>Search</span></a></li><li><a href=/links/><svg class="icon icon-tabler icon-tabler-link" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentcolor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z"/><path d="M10 14a3.5 3.5.0 005 0l4-4a3.5 3.5.0 00-5-5l-.5.5"/><path d="M14 10a3.5 3.5.0 00-5 0l-4 4a3.5 3.5.0 005 5l.5-.5"/></svg>
|
|
<span>Links</span></a></li><li><a href=/cheatsheet/><svg class="icon icon-tabler icon-tabler-infinity" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentcolor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z"/><path d="M9.828 9.172a4 4 0 100 5.656A10 10 0 0012 12a10 10 0 012.172-2.828 4 4 0 110 5.656A10 10 0 0112 12 10 10 0 009.828 9.172"/></svg>
|
|
<span>Cheatsheets</span></a></li><li><a href=/projects/><svg class="icon icon-tabler icon-tabler-clock" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentcolor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z"/><circle cx="12" cy="12" r="9"/><polyline points="12 7 12 12 15 15"/></svg>
|
|
<span>Projects</span></a></li><li><a href=/archives/><svg class="icon icon-tabler icon-tabler-archive" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentcolor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z"/><rect x="3" y="4" width="18" height="4" rx="2"/><path d="M5 8v10a2 2 0 002 2h10a2 2 0 002-2V8"/><line x1="10" y1="12" x2="14" y2="12"/></svg>
|
|
<span>Archives</span></a></li><li><a href=/about/><svg class="icon icon-tabler icon-tabler-user" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentcolor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z"/><circle cx="12" cy="7" r="4"/><path d="M6 21v-2a4 4 0 014-4h4a4 4 0 014 4v2"/></svg>
|
|
<span>About</span></a></li><li class=menu-bottom-section><ol class=menu><li id=dark-mode-toggle><svg class="icon icon-tabler icon-tabler-toggle-left" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentcolor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z"/><circle cx="8" cy="12" r="2"/><rect x="2" y="6" width="20" height="12" rx="6"/></svg>
|
|
<svg class="icon icon-tabler icon-tabler-toggle-right" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentcolor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z"/><circle cx="16" cy="12" r="2"/><rect x="2" y="6" width="20" height="12" rx="6"/></svg>
|
|
<span>Dark Mode</span></li></ol></li></ol></aside><aside class="sidebar right-sidebar sticky"><section class="widget archives"><div class=widget-icon><svg class="icon icon-tabler icon-tabler-hash" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentcolor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z"/><line x1="5" y1="9" x2="19" y2="9"/><line x1="5" y1="15" x2="19" y2="15"/><line x1="11" y1="4" x2="7" y2="20"/><line x1="17" y1="4" x2="13" y2="20"/></svg></div><h2 class="widget-title section-title">Table of contents</h2><div class=widget--toc><nav id=TableOfContents><ol><li><a href=#the-rdflib-way>The <code>rdflib</code> way</a></li><li><a href=#the-object-oriented-way>The object-oriented way</a></li><li><a href=#a-hybrid-approach>A hybrid approach</a></li></ol></nav></div></section></aside><main class="main full-width"><article class=main-article><header class=article-header><div class=article-details><header class=article-category><a href=/categories/programming/ style=background-color:gold;color:#000>Programming</a></header><div class=article-title-wrapper><h2 class=article-title><a href=/p/bridging-rdf-json-ld-and-dataclasses/>Bridging RDF, JSON-LD and Dataclasses</a></h2></div><footer class=article-time><div><svg class="icon icon-tabler icon-tabler-calendar-time" width="56" height="56" viewBox="0 0 24 24" stroke-width="2" stroke="currentcolor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z"/><path d="M11.795 21H5a2 2 0 01-2-2V7a2 2 0 012-2h12a2 2 0 012 2v4"/><circle cx="18" cy="18" r="4"/><path d="M15 3v4"/><path d="M7 3v4"/><path d="M3 11h16"/><path d="M18 16.496V18l1 1"/></svg>
|
|
<time class=article-time--published>26 Feb 2025</time></div><div><svg class="icon icon-tabler icon-tabler-clock" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentcolor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z"/><circle cx="12" cy="12" r="9"/><polyline points="12 7 12 12 15 15"/></svg>
|
|
<time class=article-time--reading>4 minute read</time></div></footer></div></header><section class=article-content><p>In the RDF world, data is expressed as a collection of triples.
|
|
These triples can contain IRIs that may or may not be accessible or valid.
|
|
And the use of these IRIs may or may not adhere to a vocabulary.
|
|
Checking the validity of the IRIs and the semantics of the triples is an additional step.</p><h2 id=the-rdflib-way>The <code>rdflib</code> way</h2><p><code>rdflib</code> only models IRIs, values and namespaces.
|
|
Developers need to be cognisant of the URIs they are using, and the vocabularies being used.
|
|
Prior to version 2.0, senpy followed a very similar model.
|
|
It had a base class to represent a generic node.
|
|
Each instance then gets its own automatically generated id, and will act like a normal dictionary, whose keys and values will be serialized as a JSON-LD dictionary.
|
|
Multiple subclasses were also included to model specific types of node, mostly to provide convenience methods for the given subtype.
|
|
Here is an example of a subclass, <code>Entity</code>.</p><div class=highlight><div class=chroma><table class=lntable><tr><td class=lntd><pre tabindex=0 class=chroma><code><span class=lnt>1
|
|
</span><span class=lnt>2
|
|
</span><span class=lnt>3
|
|
</span><span class=lnt>4
|
|
</span><span class=lnt>5
|
|
</span></code></pre></td><td class=lntd><pre tabindex=0 class=chroma><code class=language-python data-lang=python><span class=line><span class=cl><span class=n>entry</span> <span class=o>=</span> <span class=n>Entry</span><span class=p>()</span>
|
|
</span></span><span class=line><span class=cl>
|
|
</span></span><span class=line><span class=cl><span class=n>entry</span><span class=p>[</span><span class=s1>'vocab:property'</span><span class=p>]</span> <span class=o>=</span> <span class=mi>25</span>
|
|
</span></span><span class=line><span class=cl>
|
|
</span></span><span class=line><span class=cl><span class=nb>print</span><span class=p>(</span><span class=n>entry</span><span class=o>.</span><span class=n>jsonld</span><span class=p>())</span>
|
|
</span></span></code></pre></td></tr></table></div></div><p>Would print something like this:</p><div class=highlight><div class=chroma><table class=lntable><tr><td class=lntd><pre tabindex=0 class=chroma><code><span class=lnt>1
|
|
</span><span class=lnt>2
|
|
</span><span class=lnt>3
|
|
</span><span class=lnt>4
|
|
</span><span class=lnt>5
|
|
</span></code></pre></td><td class=lntd><pre tabindex=0 class=chroma><code class=language-json data-lang=json><span class=line><span class=cl><span class=p>{</span>
|
|
</span></span><span class=line><span class=cl> <span class=nt>"@id"</span><span class=p>:</span> <span class=s2>":Entry_202505...."</span><span class=p>,</span>
|
|
</span></span><span class=line><span class=cl> <span class=nt>"@type"</span><span class=p>:</span> <span class=s2>"prefix:Entity"</span><span class=p>,</span>
|
|
</span></span><span class=line><span class=cl> <span class=nt>"vocab:property"</span><span class=p>:</span> <span class=mi>25</span>
|
|
</span></span><span class=line><span class=cl><span class=p>}</span>
|
|
</span></span></code></pre></td></tr></table></div></div><p>Producing correct triples using this model requires using the vocabularies and URIs properly, with little to no tooling to enforce it.
|
|
This poses a big problem for a tool like Senpy, which aims to make it easier for professionals without a background in RDF to build and consume semantic NLP ser
|
|
If an attribute is not a URI and is not included in the global JSON-LD context, it will not generate a triple in the final graph.
|
|
Moreover, there is way to enforce that the vocabularies and the</p><p>Pros:</p><ul><li>Flexible/extensible</li><li>Lightweight. This is mostly JSON-LD in Python’s clothing.</li><li>Naturally maps to both <code>rdflib</code> and writing <code>json-ld</code></li></ul><p>Cons:</p><ul><li>Discoverability. Documentation and examples are needed to know which attributes to use</li><li>Error-prone. It is easy to misuse a property, or introduce typos</li><li>Tight coupling with semantics/RDF. One needs to know a thing or two about RDF, especially if new vocabularies or annotations need to be used.</li></ul><h2 id=the-object-oriented-way>The object-oriented way</h2><p>An obvious alternative to this problem in an object-oriented language like python is to use classes to represent our data model.
|
|
These classes can define the specific attributes available, and typing annotations can serve both as a guide for the developer, and as a means to automatically
|
|
validate objects at runtime.
|
|
There are tools like <a class=link href=https://pydantic.dev/ target=_blank rel=noopener>pydantic</a> that make this process very simple.
|
|
Then, we only need to define how your models should be serialized into JSON-LD.
|
|
We can thoroughly test this serialization to ensure that the resulting object is correct and produces the right RDF graph.
|
|
Going back to our previous example, we could define an Entry class as a dataclass, and define all the possible types of annotations as attributes.</p><p>This model works great when all the possible attributes are known ahead of time.
|
|
But it starts to break when the model provided is not comprehensive enough, or customers of your library need to provide their own ad-hoc annotations / attribut
|
|
es.
|
|
This could be solved by encouring consumers of our library to define their own subclasses whenever they need to add new attributes.
|
|
This works perfectly fine for serialization, but it breaks if your library needs to automatically deserialize these subclasses.
|
|
It also breaks if different parts of the code need to add their own attributes on the same data at the same time.
|
|
This was precisely the case of <code>senpy</code>, where entities are annotated by different plugins, each providing a different set of annotations.</p><p>Pros:</p><ul><li>Discoverability. All possible attributes are known ahead of time, including their possible types.</li><li>Decoupling from RDF. Developers only need to know about the dataclasses provided. The mapping to the RDF world is already encoded in the dataclass.</li></ul><p>Cons:</p><ul><li>Rigidity. Adding new types of annotations requires modifying the models, in the main module.</li><li>Polymorphism.</li></ul><h2 id=a-hybrid-approach>A hybrid approach</h2><p>Whichever solution is chosen in the end, it needs to:</p><ul><li>Make it easy and error-proof to add the most common types of annotations</li><li>Allow for additional annotations/attributes to be added</li><li>Allow for upgrades in the future. i.e., converting the most common custom annotations into built-in ones</li><li>Allow for deserialization of custom types</li><li>Allow multiple consumers to add their own annotations</li></ul></section><footer class=article-footer><section class=article-tags><a href=/tags/rdf/>Rdf</a>
|
|
<a href=/tags/json-ld/>Json-Ld</a>
|
|
<a href=/tags/pydantic/>Pydantic</a>
|
|
<a href=/tags/python/>Python</a></section><section class=article-copyright><svg class="icon icon-tabler icon-tabler-copyright" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentcolor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z"/><circle cx="12" cy="12" r="9"/><path d="M14.5 9a3.5 4 0 100 6"/></svg>
|
|
<span>Licensed under CC BY-NC-SA 4.0</span></section></footer><link rel=stylesheet href=https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css integrity=sha384-n8MVd4RsNIU0tAv4ct0nTaAbDJwPJzDEaqSD1odI+WdtXRGWt2kTvGFasHpSy3SV crossorigin=anonymous><script src=https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.js integrity=sha384-XjKyOOlGwcjNTAIQHIpgOno0Hl1YQqzUOEleOLALmuqehneUG+vnGctmUb0ZY0l8 crossorigin=anonymous defer></script><script src=https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/contrib/auto-render.min.js integrity=sha384-+VBxd3r6XgURycqtZ117nYw44OOcIax56Z4dCRWbxyPt0Koah1uHoK0o4+/RRE05 crossorigin=anonymous defer></script><script>window.addEventListener("DOMContentLoaded",()=>{const e=document.querySelector(".main-article");renderMathInElement(e,{delimiters:[{left:"$$",right:"$$",display:!0},{left:"$",right:"$",display:!1},{left:"\\(",right:"\\)",display:!1},{left:"\\[",right:"\\]",display:!0}],ignoredClasses:["gist"]})})</script></article><aside class=related-content--wrapper><h2 class=section-title>Related content</h2><div class=related-content><div class="flex article-list--tile"><article class=has-image><a href=/cheatsheet/python/><div class=article-image><img src=/img/python.png loading=lazy data-key data-hash=/img/python.png></div><div class=article-details><h2 class=article-title>Python</h2></div></a></article><article class=has-image><a href=/p/uv-one-rust-tool-to-rule-all-pythons/><div class=article-image><img src=/img/uv.png loading=lazy data-key data-hash=/img/uv.png></div><div class=article-details><h2 class=article-title>uv - One rust tool to rule all pythons</h2></div></a></article><article><a href=/p/nix-recipe-for-python-projects/><div class=article-details><h2 class=article-title>Nix Recipe for Python Projects</h2></div></a></article><article><a href=/p/progress-bars-in-python/><div class=article-details><h2 class=article-title>Progress bars in python</h2></div></a></article><article><a href=/p/proxies-with-apache-and-python/><div class=article-details><h2 class=article-title>Proxies with Apache and python</h2></div></a></article></div></div></aside><script src=https://giscus.app/client.js data-repo=balkian/balkian.github.com data-repo-id=MDEwOlJlcG9zaXRvcnk2OTQxMTEw data-category="Blog comments" data-category-id=DIC_kwDOAGnpts4Cnm1b data-mapping=pathname data-strict=0 data-reactions-enabled=1 data-emit-metadata=0 data-input-position=top data-theme=light data-lang=en data-loading crossorigin=anonymous async></script><script>function setGiscusTheme(e){let t=document.querySelector("iframe.giscus-frame");t&&t.contentWindow.postMessage({giscus:{setConfig:{theme:e}}},"https://giscus.app")}(function(){addEventListener("message",t=>{if(event.origin!=="https://giscus.app")return;e()}),window.addEventListener("onColorSchemeChange",e);function e(){setGiscusTheme(document.documentElement.dataset.scheme==="light"?"light":"dark_dimmed")}})()</script><footer class=site-footer><section class=copyright>©
|
|
2012 -
|
|
2025 J. Fernando Sánchez</section><section class=powerby>Built with <a href=https://gohugo.io/ target=_blank rel=noopener>Hugo</a><br>Theme <b><a href=https://github.com/CaiJimmy/hugo-theme-stack target=_blank rel=noopener data-version=3.30.0>Stack</a></b> designed by <a href=https://jimmycai.com target=_blank rel=noopener>Jimmy</a></section></footer><div class=pswp tabindex=-1 role=dialog aria-hidden=true><div class=pswp__bg></div><div class=pswp__scroll-wrap><div class=pswp__container><div class=pswp__item></div><div class=pswp__item></div><div class=pswp__item></div></div><div class="pswp__ui pswp__ui--hidden"><div class=pswp__top-bar><div class=pswp__counter></div><button class="pswp__button pswp__button--close" title="Close (Esc)"></button>
|
|
<button class="pswp__button pswp__button--share" title=Share></button>
|
|
<button class="pswp__button pswp__button--fs" title="Toggle fullscreen"></button>
|
|
<button class="pswp__button pswp__button--zoom" title="Zoom in/out"></button><div class=pswp__preloader><div class=pswp__preloader__icn><div class=pswp__preloader__cut><div class=pswp__preloader__donut></div></div></div></div></div><div class="pswp__share-modal pswp__share-modal--hidden pswp__single-tap"><div class=pswp__share-tooltip></div></div><button class="pswp__button pswp__button--arrow--left" title="Previous (arrow left)">
|
|
</button>
|
|
<button class="pswp__button pswp__button--arrow--right" title="Next (arrow right)"></button><div class=pswp__caption><div class=pswp__caption__center></div></div></div></div></div><script src=https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe.min.js integrity="sha256-ePwmChbbvXbsO02lbM3HoHbSHTHFAeChekF1xKJdleo=" crossorigin=anonymous defer></script><script src=https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe-ui-default.min.js integrity="sha256-UKkzOn/w1mBxRmLLGrSeyB4e1xbrp4xylgAWb3M42pU=" crossorigin=anonymous defer></script><link rel=stylesheet href=https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/default-skin/default-skin.min.css crossorigin=anonymous><link rel=stylesheet href=https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe.min.css crossorigin=anonymous></main></div><script src=https://cdn.jsdelivr.net/npm/node-vibrant@3.1.6/dist/vibrant.min.js integrity="sha256-awcR2jno4kI5X0zL8ex0vi2z+KMkF24hUW8WePSA9HM=" crossorigin=anonymous></script><script type=text/javascript src=/ts/main.1e9a3bafd846ced4c345d084b355fb8c7bae75701c338f8a1f8a82c780137826.js defer></script><script>(function(){const e=document.createElement("link");e.href="https://fonts.googleapis.com/css2?family=Lato:wght@300;400;700&display=swap",e.type="text/css",e.rel="stylesheet",document.head.appendChild(e)})()</script></body></html> |