<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Apruzzese Francesco</title>
    <description>The latest articles on DEV Community by Apruzzese Francesco (@opencode).</description>
    <link>https://dev.to/opencode</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F147504%2Ff5fd3d30-59cd-4694-b689-b19160dd1df8.jpeg</url>
      <title>DEV Community: Apruzzese Francesco</title>
      <link>https://dev.to/opencode</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/opencode"/>
    <language>en</language>
    <item>
      <title>Tools for reviewers</title>
      <dc:creator>Apruzzese Francesco</dc:creator>
      <pubDate>Tue, 23 Jul 2024 14:58:49 +0000</pubDate>
      <link>https://dev.to/opencode/tools-for-reviewers-hle</link>
      <guid>https://dev.to/opencode/tools-for-reviewers-hle</guid>
      <description>&lt;p&gt;In an article I wrote on &lt;a href="https://www.linkedin.com/pulse/code-review-tra-realt%25C3%25A0-e-leggenda-francesco-apruzzese/?trackingId=NJPLaPXuQa60LsbR3OSb9Q%3D%3D" rel="noopener noreferrer"&gt;Linkedin&lt;/a&gt; (I apologise if the article is in Italian), I talked about some rules I think it is good to keep in mind when reviewing a PR. They are not laws but simple suggestions on what my approach to the subject is.&lt;/p&gt;

&lt;p&gt;Here instead I would like to talk about the tools and methods I use to review code hosted on Github.&lt;/p&gt;

&lt;p&gt;Working in a large and structured company puts a lot of strain on the review phase. The number of open PRs, in line with the number of active projects, is quite a challenge. Keeping an eye on things, especially when you are senior and cross-interested in several projects is not easy.&lt;/p&gt;

&lt;p&gt;So how do you make sure you don't lose sight of any information?&lt;/p&gt;

&lt;h1&gt;
  
  
  Email
&lt;/h1&gt;

&lt;p&gt;The first step is to keep emails in order. It may sound trivial, but when you follow several repositories, the number of emails you receive is really high.&lt;/p&gt;

&lt;p&gt;Thanks to the use of Gmail's filters and labels and the separate view by label, I have been able to dedicate areas of my mailbox to just checking code-related messages.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnmf062xvl7gbaghv9ob6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnmf062xvl7gbaghv9ob6.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see from the screenshot, I have two separate streams regarding the two organisations I follow the most. The first is my company repos and the second is the &lt;a href="https://github.com/OCA/l10n-italy" rel="noopener noreferrer"&gt;Italian OCA community&lt;/a&gt; Odoo repository. The rest of the repos, being secondary and being able to wait longer for a review, are delivered already read inside my email box so as not to generate further noise.&lt;/p&gt;

&lt;p&gt;I use the &lt;a href="https://missiveapp.com/blog/inbox-zero" rel="noopener noreferrer"&gt;Inbox Zero&lt;/a&gt; approach and this configuration helps me to arrive at the end of the week with all the PRs visited and the box empty.&lt;/p&gt;

&lt;h1&gt;
  
  
  Github Interface
&lt;/h1&gt;

&lt;p&gt;I am not a big fan of the Github interface when it comes to PR. I find some things unclear and it is very lacking in information but in the long run, you actually get used to it quite easily.&lt;br&gt;
When I have some downtime (e.g. waiting for automated tests to finish) I usually open a bookmark in which I set up a filter for the PRs I'm interested in seeing on the fly. This allows me to jump straight to those that I consider to be the most urgent and that need of my intervention as soon as possible.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqgngk98hdrfytspbfv45.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqgngk98hdrfytspbfv45.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This screen helps me understand the situation of active developments and how much work is accumulating as we go along.&lt;/p&gt;

&lt;p&gt;In order to obtain only the PRs I consider important, I created a filter similar to the one below:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;is:pr archived:false is:open draft:false user:YOUR_GITHUB_ORG -author:app/dependabot -label:"Don't merge"&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;is:pr&lt;/code&gt; - is used to filter only PRs (no issues)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;archived:false&lt;/code&gt; and &lt;code&gt;is_open&lt;/code&gt; and &lt;code&gt;draft:false&lt;/code&gt; - are used to filter only active PRs&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;user:YOUR_GITHUB_ORG&lt;/code&gt; - In our company, each PR is opened on a dedicated branch within the organisation so that anyone can always be ready to take charge of the change for the most diverse cases. In this case I am only filtering the company's PRs.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-author:app/dependabot&lt;/code&gt; - all automatic dependabot PRs are excluded&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-label:"Don't merge"&lt;/code&gt; - We use a dedicated label to block any possible merge if there is still work to be done on PR. By doing so, we exclude all PRs with that label.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Github CLI with Dash extension
&lt;/h1&gt;

&lt;p&gt;OK. Here we go. Here we finally come to my favourite instrument. Being a terminal lover, I try to get my whole life flowing in there! &lt;a href="https://cli.github.com/" rel="noopener noreferrer"&gt;Github CLI&lt;/a&gt;, known as GH, allows me to perform a number of tasks with Github repos that I need to maintain more easily, plus it allows me to take advantage of some very interesting extensions. One of these is &lt;a href="https://dlvhdr.github.io/gh-dash/" rel="noopener noreferrer"&gt;Dash&lt;/a&gt;. Thanks to Dash, I have converted the filters I also use as an interface into a much more comprehensive collection of information and, thanks to the use of the keyboard, I can easily move between PRs and perform a large number of tasks.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs2pt5ab7n7pwft316060.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs2pt5ab7n7pwft316060.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;My collection of PRs to a dedicated tab that distinguishes it from any other collections (1)&lt;/p&gt;

&lt;p&gt;In the central part I have all the necessary information about PRs thanks to the presence of the repo, title, author and situation on checks and changes present. This helps me focus on PRs that have already passed the various checks or smaller PRs when I have less time. (2)&lt;/p&gt;

&lt;p&gt;With the main recap I know where the PR started and where it is about to be merged. (3)&lt;/p&gt;

&lt;p&gt;The body of the is always clearly visible. There, often, there is useful information to know to understand some things about the development contained in the PR. (4)&lt;/p&gt;

&lt;p&gt;Our CI/CD checks are critical to the conclusion of the PR life cycle, and having them in sight makes it easier. (5)&lt;/p&gt;

&lt;p&gt;I can read comments left by other reviewers or see responses to comments previously made by me. (6)&lt;/p&gt;

&lt;h1&gt;
  
  
  Discord
&lt;/h1&gt;

&lt;p&gt;A small mention should be made of the integration we have created between Discord and Github. This allows us to see the continuous flow of PRs arising and their evolution.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusions
&lt;/h1&gt;

&lt;p&gt;I always reiterate how critical the review phase is to successful projects and to truly quality code. Using the right tools and &lt;br&gt;
processes can really make everyone's life easier.&lt;/p&gt;

&lt;p&gt;This may not be the perfect list, and there are plenty of tools and services out there that help so much. This is just a look at what I personally use and the tools that have helped me the most over the years.&lt;/p&gt;

&lt;p&gt;If you too use any tools that help you in this task let me know in the comments!&lt;/p&gt;

</description>
      <category>git</category>
      <category>github</category>
      <category>pr</category>
    </item>
    <item>
      <title>Don't break your (and others) Odoo XML</title>
      <dc:creator>Apruzzese Francesco</dc:creator>
      <pubDate>Mon, 06 Jul 2020 07:19:32 +0000</pubDate>
      <link>https://dev.to/opencode/don-t-break-your-and-others-odoo-xml-373i</link>
      <guid>https://dev.to/opencode/don-t-break-your-and-others-odoo-xml-373i</guid>
      <description>&lt;p&gt;Every time I have to install third-party modules in Odoo, I start praying for 12 hours in advance.&lt;/p&gt;

&lt;p&gt;It is not uncommon, in fact, that the installation of some modules leads to the breaking of pre-existing views and, in the presence of already customized views, the risk becomes even greater.&lt;/p&gt;

&lt;p&gt;The more view inheritance is nested, the more likely it is that a programmer in the world will die.&lt;/p&gt;

&lt;p&gt;I'll list a series of simple rules to follow to avoid releasing code that could kill some installations and that doesn't make us bring on our conscience the lives of our colleagues distributed around the world.&lt;/p&gt;

&lt;h1&gt;
  
  
  Replace
&lt;/h1&gt;

&lt;p&gt;&lt;code&gt;Replace&lt;/code&gt; is like a bazooka. It destroys everything. That means if someone after us tries to find what we replaced, they'll never find it. It is not difficult to understand that &lt;code&gt;replace&lt;/code&gt; should be used sparingly and that, if possible, it should not be used at all. It is rare that the only solution to our problem is to replace a node. It is much more elegant to hide it with due care and with much more elegance.&lt;/p&gt;

&lt;h1&gt;
  
  
  Add attributes
&lt;/h1&gt;

&lt;p&gt;Let's imagine that we have a div, with its own class, to which we want to add a new one. What often happens is that we use the "replace" to replace the node with the same node to which we add our class. This is wrong for two reasons: the original node is no longer recognizable from the previous &lt;code&gt;xpath&lt;/code&gt; and two modules that apply the same modification tend to obscure each other.&lt;/p&gt;

&lt;p&gt;The best thing to do is to use the addition of value through the use of attributes.&lt;/p&gt;

&lt;p&gt;Wrong:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;

&lt;span class="nt"&gt;&amp;lt;xpath&lt;/span&gt; &lt;span class="na"&gt;expr=&lt;/span&gt;&lt;span class="s"&gt;"//div[@id='node_id']"&lt;/span&gt; &lt;span class="na"&gt;position=&lt;/span&gt;&lt;span class="s"&gt;"replace"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"node_id"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"row d-none"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/xpath&amp;gt;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Right:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;

&lt;span class="nt"&gt;&amp;lt;xpath&lt;/span&gt; &lt;span class="na"&gt;expr=&lt;/span&gt;&lt;span class="s"&gt;"//div[@id='node_id']"&lt;/span&gt; &lt;span class="na"&gt;position=&lt;/span&gt;&lt;span class="s"&gt;"attributes"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;attribute&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"class"&lt;/span&gt; &lt;span class="na"&gt;separator=&lt;/span&gt;&lt;span class="s"&gt;" "&lt;/span&gt; &lt;span class="na"&gt;add=&lt;/span&gt;&lt;span class="s"&gt;"d-none"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/xpath&amp;gt;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;You can use it to extend if condition in view, too.&lt;/p&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;

&lt;span class="nt"&gt;&amp;lt;xpath&lt;/span&gt; &lt;span class="na"&gt;expr=&lt;/span&gt;&lt;span class="s"&gt;"//button[@id='o_payment_form_pay']"&lt;/span&gt; &lt;span class="na"&gt;position=&lt;/span&gt;&lt;span class="s"&gt;"attributes"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;attribute&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"t-if"&lt;/span&gt; &lt;span class="na"&gt;separator=&lt;/span&gt;&lt;span class="s"&gt;" "&lt;/span&gt; &lt;span class="na"&gt;add=&lt;/span&gt;&lt;span class="s"&gt;"and accept_terms_conditions"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/xpath&amp;gt;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h1&gt;
  
  
  Hasclass
&lt;/h1&gt;

&lt;p&gt;As we have seen previously, a node can be extended by adding a new class. This highlights another problem. Very often we refer to nodes by indicating their class.&lt;/p&gt;

&lt;p&gt;Example:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;

&lt;span class="nt"&gt;&amp;lt;xpath&lt;/span&gt; &lt;span class="na"&gt;expr=&lt;/span&gt;&lt;span class="s"&gt;"//div[@class='myclass']"&lt;/span&gt; &lt;span class="na"&gt;position=&lt;/span&gt;&lt;span class="s"&gt;"..."&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    ...
&lt;span class="nt"&gt;&amp;lt;/xpath&amp;gt;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;If a module, before ours, adds a new class to the node we are looking for, we may never find it again. In this case, the solution is given by the &lt;code&gt;hasclass&lt;/code&gt; function that allows us to control the nodes that have the class we are looking for among all those available.&lt;/p&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;

&lt;span class="nt"&gt;&amp;lt;xpath&lt;/span&gt; &lt;span class="na"&gt;expr=&lt;/span&gt;&lt;span class="s"&gt;"//div[hasclass('myclass')]"&lt;/span&gt; &lt;span class="na"&gt;position=&lt;/span&gt;&lt;span class="s"&gt;"..."&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    ...
&lt;span class="nt"&gt;&amp;lt;/xpath&amp;gt;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h1&gt;
  
  
  ID and Name abuse (Do it!)
&lt;/h1&gt;

&lt;p&gt;When using &lt;code&gt;xpath&lt;/code&gt;, you always try to hook into a node that is uniquely recognizable throughout the entire XML structure. This is not always so easy because some nodes are very anonymous (they have too generic class, they are the same as other nodes in other places, they are repeated in the structure).&lt;/p&gt;

&lt;p&gt;In this case, it is a good practice to use the &lt;code&gt;id&lt;/code&gt; or &lt;code&gt;name&lt;/code&gt; attribute on nodes that we believe can be hooked so as to facilitate the work of developers who will extend our views.&lt;/p&gt;
&lt;h1&gt;
  
  
  Do not obscure
&lt;/h1&gt;

&lt;p&gt;When the starting structure is very complex and there are many changes to be made, there may be the temptation to obscure a view by creating a new one with the same XML ID. It is useless to explain how dangerous and harmful this practice is. The XML ID is the parameter used to inherit views and forms that come after yours would expect a structure that is actually completely disrupted.&lt;/p&gt;

&lt;p&gt;If there are so many changes to make, you're probably not using the correct view and the best way is to create a new one and convince the software to use yours instead of the original one.&lt;/p&gt;
&lt;h1&gt;
  
  
  Variables, not hardcore
&lt;/h1&gt;

&lt;p&gt;Whenever you need a value to perform operations (compare values, numbers of cycles for a loop, etc.) it would be convenient to use variables so that the behavior in the operations can be more easily modified.&lt;/p&gt;

&lt;p&gt;Wrong:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;

&lt;span class="nt"&gt;&amp;lt;ul&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;t&lt;/span&gt; &lt;span class="na"&gt;t-foreach=&lt;/span&gt;&lt;span class="s"&gt;"['a', 'b', 'c']"&lt;/span&gt; &lt;span class="na"&gt;t-as=&lt;/span&gt;&lt;span class="s"&gt;"val"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;t-esc=&lt;/span&gt;&lt;span class="s"&gt;"val"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/t&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Right:&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;

&lt;p&gt;&lt;span class="nt"&gt;&amp;lt;t&lt;/span&gt; &lt;span class="na"&gt;t-set=&lt;/span&gt;&lt;span class="s"&gt;"vals_to_loop"&lt;/span&gt; &lt;span class="na"&gt;t-value=&lt;/span&gt;&lt;span class="s"&gt;"['a', 'b', 'c']"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;&lt;br&gt;
&lt;span class="nt"&gt;&amp;lt;ul&amp;gt;&lt;/span&gt;&lt;br&gt;
    &lt;span class="nt"&gt;&amp;lt;t&lt;/span&gt; &lt;span class="na"&gt;t-foreach=&lt;/span&gt;&lt;span class="s"&gt;"vals_to_loop"&lt;/span&gt; &lt;span class="na"&gt;t-as=&lt;/span&gt;&lt;span class="s"&gt;"val"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;&lt;br&gt;
        &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;t-esc=&lt;/span&gt;&lt;span class="s"&gt;"val"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&amp;lt;/li&amp;gt;&lt;/span&gt;&lt;br&gt;
    &lt;span class="nt"&gt;&amp;lt;/t&amp;gt;&lt;/span&gt;&lt;br&gt;
&lt;span class="nt"&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;&lt;/p&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h1&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Final considerations&lt;br&gt;
&lt;/h1&gt;

&lt;p&gt;Unfortunately, this list is only part of the good practice to keep in mind. No code is perfect, and in many cases, even Odoo's core code is not free from these problems.&lt;/p&gt;

&lt;p&gt;I only hope that these rules can serve as a springboard for writing better code.&lt;/p&gt;

&lt;h1&gt;
  
  
  Support
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://www.buymeacoffee.com/scapigliato" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.buymeacoffee.com%2Fbuttons%2Flato-blue.png" alt="Buy Me A Coffee"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>odoo</category>
      <category>xml</category>
      <category>xpath</category>
      <category>view</category>
    </item>
    <item>
      <title>Speed up your Odoo 12.0 code</title>
      <dc:creator>Apruzzese Francesco</dc:creator>
      <pubDate>Mon, 23 Mar 2020 08:58:43 +0000</pubDate>
      <link>https://dev.to/opencode/speed-up-your-odoo-code-4abn</link>
      <guid>https://dev.to/opencode/speed-up-your-odoo-code-4abn</guid>
      <description>&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;I love Python and I work with Odoo since 10 years.&lt;br&gt;
In all these years I wrote a lot of code for Odoo, both modules and migrations scripts. Every time you need to move a lot of data from a source to a destination, time is an important point.&lt;/p&gt;

&lt;p&gt;For example, I once wrote a script to copy more than 20 millions of products from an external software to Odoo database. Odoo executes a lot of checks every time you create a new record, so it's important to understand that every seconds is vital.&lt;/p&gt;

&lt;p&gt;With so many records, every millisecond is a resource.&lt;/p&gt;

&lt;p&gt;When we're writing the code, what we do is create the necessary data and run the tests. These data are often some products, partners, orders and invoices. When everything works you can create automatic tests.&lt;br&gt;
I don't think every time we develop something we create a stress test for a large amount of data.&lt;/p&gt;

&lt;p&gt;So, I always study and try new tips and tricks to increase speed of my code. A code is never perfect and it can be improved, always!&lt;/p&gt;

&lt;p&gt;Using this basic script I want to show you some snippets to speed up code effortlessly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;timeit&lt;/span&gt;

&lt;span class="c1"&gt;# Here an exemple of common but slow code
&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;CODE&lt;/span&gt; &lt;span class="n"&gt;SLOW&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="c1"&gt;# Here an exemple of fast code
&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;CODE&lt;/span&gt; &lt;span class="n"&gt;FAST&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="n"&gt;setup&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"records = env['res.partner'].search([], limit=1000)"&lt;/span&gt;
&lt;span class="n"&gt;repetitions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;globals_vals&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;'env'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;repetition&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;repetitions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;slow_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;timeit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;repeat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;code_slow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;setup&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;repetition&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nb"&gt;globals&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;'env'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;fast_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;timeit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;repeat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;code_fast&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;setup&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;repetition&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nb"&gt;globals&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;globals_vals&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;f'=============== loops &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;repetition&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;slow_time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fast_time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h1&gt;
  
  
  set vs recordset
&lt;/h1&gt;

&lt;p&gt;Working with Odoo means using a lots of recordsets. A recordset is an object with datas, functions and methods. It is a set of data, either from a database or calculated, on steroids. If you know other ORMs, you know what I'm talking about.&lt;/p&gt;

&lt;p&gt;Sometimes, however, using the operations that the framework makes available is not convenient and it's more advantageous to use directly what python offers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;code_slow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"""
result = env['res.partner'].browse()
for record in records:
    result |= record
"""&lt;/span&gt;

&lt;span class="n"&gt;code_fast&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"""
result = set()
for record in records:
    result.add(record.id)
env['res.partner'].browse(result)
"""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;=============== loops 1
[0.13306364299933193, 0.10838474300544476, 0.10796952999953646, 0.10946986099588685, 0.10623473400482908]
[0.0008965670058387332, 0.000925485001062043, 0.0009349410029244609, 0.0009159730034298263, 0.0009303710030508228]
=============== loops 10
[1.106517462998454, 1.1065070289987489, 1.1934943989981548, 1.1571569709994947, 1.1170753799960949]
[0.009077082999283448, 0.009004210005514324, 0.009018140997795854, 0.009551764000207186, 0.009227231996192131]
=============== loops 100
[11.516509660999873, 11.74818367199623, 11.85345141900325, 12.856167154001014, 13.63239839200105]
[0.09894232600345276, 0.09653759599314071, 0.09699099600402405, 0.10923265099700075, 0.10307022200140636]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h1&gt;
  
  
  write vs field settings
&lt;/h1&gt;

&lt;p&gt;When you have a recordset, it's possible to change a value with a simple assignment. This is handy but every time you use it, Odoo prepare a query and change this value in cache. When you need to write more values ​​in the same recordset or the same value on more recordsets, you must use write function.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;code_slow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"""
for record in records:
    record.ref = 'test_speed'
"""&lt;/span&gt;

&lt;span class="n"&gt;code_fast&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"""
records.write({'ref': 'test_speed'})
"""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;=============== loops 1
[2.9913442569959443, 0.9559976780001307, 0.900353463999636, 0.8353457880002679, 1.0130259670040687]
[0.13637410899536917, 0.1379215750057483, 0.14272761800384615, 0.12462920800317079, 0.13321589499537367]
=============== loops 10
[10.402880988003744, 11.000012251999578, 11.697913458003313, 11.106917288998375, 11.169360947998939]
[2.0335192759957863, 2.4452823780011386, 2.2474471349996747, 2.5181185900000855, 2.776447412004927]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h1&gt;
  
  
  list comprehension vs mapped
&lt;/h1&gt;

&lt;p&gt;Among the recordset methods, we have &lt;code&gt;mapped()&lt;/code&gt;. It creates a set of values from a recordset. Values got from &lt;code&gt;mapped()&lt;/code&gt; can be navigated through Odoo relations. In simple cases, &lt;code&gt;mapped()&lt;/code&gt; is as convenient as list comprehension. In complex cases (for example, a navigation of relations of 3 levels) &lt;code&gt;mapped()&lt;/code&gt; is more convenient to use.&lt;/p&gt;

&lt;p&gt;It gives always better readability and this is good for maintainability.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;code_slow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"""
states_set = set([r.state_id.id for r in records if r.state_id])
states = env['res.country.state'].browse(states_set)
"""&lt;/span&gt;

&lt;span class="n"&gt;code_fast&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"""
states = records.mapped('state_id')
"""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;=============== loops 1
[0.31179145100031747, 0.004711384994152468, 0.004819055000552908, 0.004665278996981215, 0.0044039049971615896]
[0.0036316699988674372, 0.003607042999647092, 0.003627748999861069, 0.0036114609974902123, 0.003590111999073997]
=============== loops 10
[0.047915923001710325, 0.04522175599413458, 0.044370734998665284, 0.04409689400199568, 0.04411724000237882]
[0.035760099002800416, 0.0357158859987976, 0.03603034999832744, 0.036463224998442456, 0.03686660800303798]
=============== loops 100
[0.45273306199669605, 0.44231640599900857, 0.43908101499982877, 0.44457509399944684, 0.4423183210019488]
[0.36031395699683344, 0.3654527219987358, 0.38619487999676494, 0.3619795150007121, 0.3626547140011098]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h1&gt;
  
  
  loop vs filtered
&lt;/h1&gt;

&lt;p&gt;Another recordset function is &lt;code&gt;filtered()&lt;/code&gt;. You can use to filter recordset with a function or a value. Like &lt;code&gt;mapped()&lt;/code&gt;, &lt;code&gt;filtered()&lt;/code&gt; is as convenient as &lt;code&gt;loop&lt;/code&gt; for simple cases but can be more convenient for complex case.&lt;/p&gt;

&lt;p&gt;It gives always better readability and this is good for maintainability.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;code_slow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"""
partners_start_with_a = set()
for record in records:
    if record.name.lower().startswith('a'):
        partners_start_with_a.add(record.id)
env['res.partner'].browse(partners_start_with_a)
"""&lt;/span&gt;

&lt;span class="n"&gt;code_fast&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"""
records.filtered(lambda r: r.name.lower().startswith('a'))
"""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;=============== loops 1
[0.3316459549969295, 0.002682621001440566, 0.0026461579982424155, 0.002589060997706838, 0.002434752997942269]
[0.002538305998314172, 0.002414796006632969, 0.0024050630017882213, 0.0024614639987703413, 0.002459411000018008]
=============== loops 10
[0.02490358999784803, 0.027465703002235387, 0.02418504800152732, 0.023117996999644674, 0.023439353004505392]
[0.02508747999672778, 0.024697505999938585, 0.02418924599624006, 0.024244852000265382, 0.024165777998859994]
=============== loops 100
[0.23826791400642833, 0.22961928599397652, 0.23434631299460307, 0.2344872630055761, 0.2336325560027035]
[0.24336347499775002, 0.24536530500336085, 0.2900283189956099, 0.24434635799843818, 0.24427578799804905]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h1&gt;
  
  
  single browse vs multi browse
&lt;/h1&gt;

&lt;p&gt;In some cases we have a list of ids and we want to get the relative recordsets. In Odoo we use &lt;code&gt;browse()&lt;/code&gt;. &lt;code&gt;browse()&lt;/code&gt; require an id or a list of ids and return a single recordset or a recordset of recordsets. If you need to iterate them you must loop directly on &lt;code&gt;browse()&lt;/code&gt; return.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;code_slow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"""
record_ids = records.ids
for record_id in record_ids:
    record = env['res.partner'].browse(record_id)
    record.name
"""&lt;/span&gt;

&lt;span class="n"&gt;code_fast&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"""
record_ids = records.ids
for record in env['res.partner'].browse(record_ids):
    record.name
"""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;=============== loops 1
[1.6325390929996502, 0.004575414001010358, 0.0046123150023049675, 0.005507702997419983, 0.004666433000238612]
[0.0024330550004378892, 0.002370230002270546, 0.0023654250035178848, 0.0024355540008400567, 0.0023724880011286587]
=============== loops 10
[0.049219961001654156, 0.04274192699813284, 0.0399551490045269, 0.039992154997889884, 0.04065825999714434]
[0.020162856999377254, 0.02014190399495419, 0.021769888000562787, 0.021047277994512115, 0.020693995997135062]
=============== loops 100
[0.41175051799655193, 0.4071878250033478, 0.4059581019973848, 0.40895022099721245, 0.40670431699982146]
[0.20786928899906343, 0.2082131749994005, 0.2086394680009107, 0.23048232900328003, 0.20772191799915163]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h1&gt;
  
  
  loop vs map
&lt;/h1&gt;

&lt;p&gt;This is a python pure tip but I use it in my script especially with math calculations. &lt;code&gt;map()&lt;/code&gt; call a function on an iterable object and return an object with results.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;code_slow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"""
def increment_id(record_id):
    return record_id + 1

for record_id in records.ids:
    increment_id(record_id)
"""&lt;/span&gt;

&lt;span class="n"&gt;code_fast&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"""
def increment_id(record_id):
    return record_id + 1

map(increment_id, records.ids)
"""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;=============== loops 10
[0.0014327949975267984, 0.0014315969965537079, 0.0014401990047190338, 0.0015307379944715649, 0.0014989250048529357]
[0.0004444369988050312, 0.00044178399548400193, 0.000439752999227494, 0.0004845689982175827, 0.0004615380021277815]
=============== loops 100
[0.016216568001254927, 0.013593563002359588, 0.012723464002192486, 0.01124695000180509, 0.011159162997500971]
[0.003178275001118891, 0.0032086909995996393, 0.003190825998899527, 0.0031917309970594943, 0.003197609999915585]
=============== loops 1000
[0.11258809099672362, 0.11201509100646945, 0.11202597500232514, 0.11326640100014629, 0.11274601000332041]
[0.034097837000445, 0.03186929500225233, 0.031756801006849855, 0.031629047996830195, 0.0315622540001641]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h1&gt;
  
  
  Conclusions
&lt;/h1&gt;

&lt;p&gt;I know you're thinking that 0.07 seconds on a line of code aren't so important but if you remember my introduction you can see that on 20 millions of repetations we can earn a lot of time!&lt;/p&gt;

&lt;p&gt;See you soon. I'm going away... very fast!&lt;/p&gt;

&lt;h1&gt;
  
  
  Support
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://www.buymeacoffee.com/scapigliato"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--PB_fknuG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.buymeacoffee.com/buttons/lato-red.png" alt="Buy Me A Coffee"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>odoo</category>
      <category>python</category>
    </item>
    <item>
      <title>TinyDB per Python - Quando JSON si crede un database</title>
      <dc:creator>Apruzzese Francesco</dc:creator>
      <pubDate>Sat, 20 Apr 2019 11:13:45 +0000</pubDate>
      <link>https://dev.to/opencode/tinydb-per-python-quando-json-si-crede-un-database-3f89</link>
      <guid>https://dev.to/opencode/tinydb-per-python-quando-json-si-crede-un-database-3f89</guid>
      <description>&lt;h2&gt;
  
  
  Premessa noiosa
&lt;/h2&gt;

&lt;p&gt;Durante la realizzazione di un progetto per un cliente (Python + GTK, giusto per curiosità) mi sono ritrovato davanti alla situazione di dover salvare dei dati collezionati durante l'uso dell'applicativo che sarebbero poi stati inviati ad un sistema ERP esterno.&lt;br&gt;
Essendo i dati comunque pochi, legati strettamente alla singola macchina e non eccessivamente interconnessi tra di loro, mi sembrava eccessivo utilizzare un database (sì, anche sqlite mi sembrava "troppo"). Dovendo però prevedere alcune situazioni di distaster più o meno complesse, anche il mantenimento delle informazioni in memoria era un via non proprio percorribile. Ho cercato così una strada che mi permettesse di salvare le informazioni in un file mantenendo però la praticità delle query e di ciò che un database sa offrire. La mia ricerca mi ha portato a scoprire TinyDB.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--lD2J98YN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://bloggie.io/rails/active_storage/blobs/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBbW9CIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--3d643de4abce118c0e31c5e1d2e23d0d6df96a81/tinydb_logo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lD2J98YN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://bloggie.io/rails/active_storage/blobs/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBbW9CIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--3d643de4abce118c0e31c5e1d2e23d0d6df96a81/tinydb_logo.png" alt="tinydb_logo"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  La parte Superquark
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://tinydb.readthedocs.io"&gt;TinyDB&lt;/a&gt; è una libreria per Python che può essere vista come la versione dei programmatori con davvero pochissime esigenze di MongoDB. Grazie all'uso di dati strutturati JSON, TinyDB permette di avere un database simulato all'interno di un comune file di testo con annesse tabelle, campi, valori e query di ricerca e gestione.&lt;/p&gt;
&lt;h3&gt;
  
  
  Punti di forza
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Possibilità di gestire i dati come dizionari essendo gli stessi salvati e caricati dal formato JSON&lt;/li&gt;
&lt;li&gt;100% codice Python senza bisogno di librerie esterne (né pip né di sistema)&lt;/li&gt;
&lt;li&gt;Uso di simil-query per interrogare e manipolare i dati&lt;/li&gt;
&lt;li&gt;Accesso ai dati organizzati in tabelle&lt;/li&gt;
&lt;li&gt;Il database è un file di testo. Lo copi, lo invii, lo apri e lo leggi.&lt;/li&gt;
&lt;li&gt;Può essere esteso per coprire ogni tipo di esigenza (creare ad esempio base di dati customizzate)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Debolezze
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Non permette di avere dati relazionati&lt;/li&gt;
&lt;li&gt;Non gestisce gli indici autoincrementali&lt;/li&gt;
&lt;li&gt;Le performance calano al crescere del file&lt;/li&gt;
&lt;li&gt;Non gestisce le scritture concorrenti&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Codice divertente
&lt;/h2&gt;

&lt;p&gt;Tralascio la parte dove si parla di installazioni così possiamo passare all'analisi di un po' di codice (finalmente!!!). &lt;/p&gt;

&lt;p&gt;Come prima cosa importiamo il minimo indispensabile per aprire il database&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;tinydb&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TinyDB&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TinyDB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'/tmp/test.tdb'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Abbiamo appena istanziato il nostro dabase generando il file necessario nel nostro sistema. Qualora il file non dovesse esistere, verrà generato con la prima chiamata utile. Recandoci nella cartella &lt;strong&gt;/tmp&lt;/strong&gt; possiamo trovare &lt;strong&gt;test.tdb&lt;/strong&gt; (il nome e l'estensione sono a discrezione di chi scrive il codice) che conterrà quanto segue&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{"_default": {}}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;_default&lt;/strong&gt; è la tabella base in cui verranno collocati tuttii record non associati ad una precisa tabella. Facciamo un esempio.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'Phil'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'age'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;45&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'Logan'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'age'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;56&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="mi"&gt;2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Il contenuto del nostro file ora sarà&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{"_default": {"1": {"age": 45, "name": "Phil"}, "2": {"age": 56, "name": "Logan"}}}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Proviamo ora a vedere come gestire le tabelle&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;superhero&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'superhero'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Ora nel nostro file sarà presente che la chiave che indica la nuova tabella&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{"_default": {
    "1": {"age": 45, "name": "Phil"},
    "2": {"age": 56, "name": "Logan"}}, 
"superhero": {}}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;A questo punto sarà possibile usare la tabella per eseguire le operazioni&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;superhero&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'Batman'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'is_cool'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;superhero&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'Superman'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'is_cool'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;superhero&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'Zuppaman'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'is_cool'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="mi"&gt;3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;È possibile ottenere tutti i valori di una tabella (o di _default interrogandlo direttamente l'istanza del db) mediante la funzione all()&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;superhero&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="s"&gt;u'is_cool'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;u'name'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;u'Batman'&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;u'is_cool'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;u'name'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;u'Superman'&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;u'is_cool'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;u'name'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;u'Zuppaman'&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Le ricerche possono essere effettuate in diversi modi. Guardiamone alcuni.&lt;/p&gt;

&lt;p&gt;Uso di ricerche semplici&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;tinidb&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Query&lt;/span&gt;

&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="c1"&gt;# Ricerca semplice
&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;superhero&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;'Superman'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="s"&gt;u'is_cool'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;u'name'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;u'Superman'&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;superhero&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="s"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;'Superman'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="s"&gt;u'is_cool'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;u'name'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;u'Superman'&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;

&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="c1"&gt;# OR tra due clausole
&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;superhero&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;'Superman'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;'Zuppaman'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="s"&gt;u'is_cool'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;u'name'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;u'Superman'&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;u'is_cool'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;u'name'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;u'Zuppaman'&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;

&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="c1"&gt;# Uso delle espressioni regolari
&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;superhero&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Bat[a-z]'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="s"&gt;u'is_cool'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;u'name'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;u'Batman'&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Clausole where&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;tinydb&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;where&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;superhero&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;'Superman'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="s"&gt;u'is_cool'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;u'name'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;u'Superman'&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;L'uso delle ricerche ci permette di gestire i dati. Con esse infatti è possibile eseguire, ad esempio, l'aggiornamento delle informazioni&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="c1"&gt;# Upserting dei dati con clausola
&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;superhero&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;upsert&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'Zuppaman'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'is_cool'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;'Zuppaman'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  Conclusioni
&lt;/h3&gt;

&lt;p&gt;Ovviamente un solo post non basterebbe per snocciolare le potenzialità di una libreria. Per questo vi rimando alla &lt;a href="https://tinydb.readthedocs.io/en/latest/getting-started.html"&gt;guida introduttiva&lt;/a&gt; e a &lt;a href="https://tinydb.readthedocs.io/en/latest/usage.html"&gt;quella più avanzata&lt;/a&gt; che spiegano davvero bene cosa è possibile fare e come farlo.&lt;/p&gt;

&lt;h3&gt;
  
  
  Supporto
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.buymeacoffee.com/scapigliato"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--x2F9K2FT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.buymeacoffee.com/buttons/lato-blue.png" alt="Buy Me A Coffee"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>json</category>
      <category>tinydb</category>
    </item>
  </channel>
</rss>
