<?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>Forem: topjer</title>
    <description>The latest articles on Forem by topjer (@topjer).</description>
    <link>https://forem.com/topjer</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%2F558652%2F61e5289d-5653-494e-8629-11a56bfd9cbd.jpg</url>
      <title>Forem: topjer</title>
      <link>https://forem.com/topjer</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/topjer"/>
    <language>en</language>
    <item>
      <title>My colleague did overtime for two weeks straight, here is what I told him</title>
      <dc:creator>topjer</dc:creator>
      <pubDate>Mon, 11 Aug 2025 19:47:26 +0000</pubDate>
      <link>https://forem.com/topjer/my-colleague-did-overtime-for-two-weeks-straight-here-is-what-i-told-him-2n3h</link>
      <guid>https://forem.com/topjer/my-colleague-did-overtime-for-two-weeks-straight-here-is-what-i-told-him-2n3h</guid>
      <description>&lt;p&gt;My vacation stand-in told me, he had to do overtime for two weeks straight in order to handle the workload and today I want to share with you how I reacted. But let's start at the beginning.&lt;/p&gt;

&lt;h2&gt;
  
  
  How did it come to this?
&lt;/h2&gt;

&lt;p&gt;It all happened in the project team I am currently leading.&lt;/p&gt;

&lt;p&gt;My vacation plans for this year were overlapping with the other senior developer in that project by two whole weeks. Which is bad! Because we both would usually be the stand in for the other. The only way this would be feasible is when we both found someone willing to keep the ball rolling while we are gone.&lt;/p&gt;

&lt;p&gt;A not-yet senior developer in the team, we both regard highly, came to mind and since he plans to join the senior ranks, we thought it would be great opportunity for him to see what it could be like.&lt;/p&gt;

&lt;p&gt;The idea was that we would prepare tasks in advance to be distributed to the other team members. This way, he could focus on supporting them. &lt;/p&gt;

&lt;p&gt;Unfortunately, it did not work out as planned. Even shortly before our vacations, it was not clear whether we could get the most important requirements to a workable state. This is due to general difficulties in the project, but this is an entirely different story.&lt;/p&gt;

&lt;p&gt;After all, we managed to formulate a plan and have some tasks ready for the upcoming two weeks. So, we took our well-deserved leave, even though we were feeling some remorse.&lt;/p&gt;

&lt;h2&gt;
  
  
  How did it go
&lt;/h2&gt;

&lt;p&gt;We rightfully placed our trust in him. The team might not have progressed slower than usual, but at least no one was twiddling their thumbs during the two weeks - which was the scenario we were afraid off the most.&lt;/p&gt;

&lt;p&gt;Once I acclimated myself back at work, I managed two have a one on one chat with the brave stand-in, so here is what he told me.&lt;/p&gt;

&lt;p&gt;In general, he did enjoy the new challenges greatly. He never had to coordinate a team before, but doing so felt very rewarding. His first priority was to support the rest of the team whenever possible. Mostly in the form of requirement clarification and code reviews.&lt;/p&gt;

&lt;p&gt;This would come at a cost though.&lt;/p&gt;

&lt;p&gt;All his time was spent while making sure everyone else is able to work. Yet, he did not want to disappoint us, so after a day full of meetings and support, he would do overtime in the evening, where he worked on his own tasks.&lt;/p&gt;

&lt;p&gt;This was going on for the entirety of the two weeks.&lt;/p&gt;

&lt;h2&gt;
  
  
  My reaction
&lt;/h2&gt;

&lt;p&gt;My first reaction was to thank him for this dedication and effort, I was thankful for him to go the extra mile.&lt;/p&gt;

&lt;p&gt;But, I was not happy, because it seemed unnecessary to me, so I told him he does not have to do that the next time.&lt;/p&gt;

&lt;p&gt;If he had not gone this extra mile and told me that his entire day would have been spend with supporting his team members, leaving no time to work on anything himself, then I would have understood that.&lt;/p&gt;

&lt;p&gt;Together with him, we would have discussed strategies on how to move his task forward, so we can make the target date.&lt;/p&gt;

&lt;p&gt;Now, you might be wondering why I make a big fuzz out of some overtime. A bit of crunch is naturally part of the job, right?&lt;/p&gt;

&lt;p&gt;To understand my aversion against overtime we have to look at ...&lt;/p&gt;

&lt;h2&gt;
  
  
  The bigger picture
&lt;/h2&gt;

&lt;p&gt;The first thing you need to understand is, that the project we are talking about is somewhat ... problematic.&lt;/p&gt;

&lt;p&gt;It started out chaotic, with no clear target picture and a lack of strong leadership. Which was the reason why I joined it and I was able to create some structure and got us moving forward. With the initial hurdle cleared, it soon became clear that it is going to be a tight one.&lt;/p&gt;

&lt;p&gt;With the months passing, the timeline becomes only more unrealistic because we are slowly unveiling what is actually expected from us.&lt;/p&gt;

&lt;p&gt;This situation creates massive pressure on our developers. Since they are still inexperienced, they do not know how to handle it. Which often means that they to compensate for the delay.&lt;/p&gt;

&lt;p&gt;But, individual contributors should never feel responsible for the state of a delayed project.&lt;/p&gt;

&lt;p&gt;It is not their fault and it is not in their power to fix it. The reasons for a delayed project rarely lie in a lack of dedication of the team. It is more likely due to bad planning, too many parallel tasks, communication issues between stakeholder etc.&lt;/p&gt;

&lt;p&gt;Instead what they should do, is to make this transparent. They should voice their concerns with a dead line that is too tight. Then we can search for ways to solve the issue as a team.&lt;/p&gt;

&lt;h1&gt;
  
  
  Closing thoughts
&lt;/h1&gt;

&lt;p&gt;Going back to the initial story, it hopefully is clearer that my reaction was the way it was, because it was the latest example of a developer doing more work than necessary. Something that is rarely seen nor appreciated.&lt;/p&gt;

&lt;p&gt;Have you experienced similar situations in the past? If so, I would really like to know how you have handled it.&lt;/p&gt;

&lt;p&gt;Want to be informed every time I publish something new? Then subscribe to my &lt;a href="https://topjer.substack.com/" rel="noopener noreferrer"&gt;substack&lt;/a&gt;&lt;/p&gt;

</description>
      <category>workplace</category>
      <category>leadership</category>
    </item>
    <item>
      <title>Introduction to monkey patching with pytest</title>
      <dc:creator>topjer</dc:creator>
      <pubDate>Mon, 16 Jun 2025 19:14:10 +0000</pubDate>
      <link>https://forem.com/topjer/introduction-to-monkey-patching-with-pytest-2db1</link>
      <guid>https://forem.com/topjer/introduction-to-monkey-patching-with-pytest-2db1</guid>
      <description>&lt;p&gt;Not too long ago, I had a pair-programming session with a colleague where we understood how monkey-patching works. A technique both of us had not really used before.&lt;/p&gt;

&lt;p&gt;In this article I want to share our findings. But I will not simply present you the final solution. Instead we will retrace the steps and incrementally improve our code. Hopefully, this will not only teach you how to use monkey-patching but also how to "work with" error messages.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;Assume we want to test a function like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# utils.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;read_file&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="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;complete_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;_foo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;name_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;complete_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;name_path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The elephant in the room
&lt;/h2&gt;

&lt;p&gt;The mentioned function comes with a big problem and that is, that it opens the file itself and thus performs interaction with the file system.&lt;br&gt;
This makes testing much harder than it needs to be. Because we want to test the logic around it and not that &lt;code&gt;Path.open&lt;/code&gt; is working properly. Yet in order to do so, we have to deal with it.&lt;/p&gt;

&lt;p&gt;Now, one could go ahead and have the function actually read a file, but this leads to follow up problems like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Will the file be cleaned up after the test?&lt;/li&gt;
&lt;li&gt;Will it also be cleaned up if something fails?&lt;/li&gt;
&lt;li&gt;What if there is already a file with the needed name?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As mentioned before, we do not actually want to test &lt;code&gt;Path.open&lt;/code&gt; so why not take it out of the equation completely?&lt;/p&gt;
&lt;h2&gt;
  
  
  About monkeypatching
&lt;/h2&gt;

&lt;p&gt;Definition of monkeypatching according to &lt;a href="https://en.wikipedia.org/wiki/Monkey_patch" rel="noopener noreferrer"&gt;wikipedia&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In computer programming, monkey patching is a technique used to dynamically update the behavior of a piece of code at run-time. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We will see how to use monkeypatching to replace functionality with mock objects that make testing easier.&lt;/p&gt;
&lt;h2&gt;
  
  
  First step
&lt;/h2&gt;

&lt;p&gt;Let us assume the following folder structure&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mp_test/
    utils.py
    testing.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;where &lt;code&gt;utils.py&lt;/code&gt; contains the code from above. As a first step, lets try to do this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# testing.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;mp_test.utils&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;read_file&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_read_file&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;read_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;test_file&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Running this test will give us the expected error&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FAILED testing.py::test_read_file - FileNotFoundError: [Errno 2] No such file or directory: 'test_file_foo'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Based on the documentation on the &lt;a href="https://docs.pytest.org/en/stable/reference/reference.html#pytest.MonkeyPatch.setattr" rel="noopener noreferrer"&gt;monkeypatch fixture&lt;/a&gt; lets try something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# testing.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;mp_test.utils&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;read_file&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_read_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;monkeypatch&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;monkeypatch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pathlib&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Path&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;read_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;test_file&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What did happen here?&lt;/p&gt;

&lt;p&gt;First of all we are using the &lt;code&gt;monkeypatch&lt;/code&gt; fixture and use it to replace how &lt;code&gt;Path&lt;/code&gt; is working in &lt;code&gt;pathlib&lt;/code&gt;. For starters, I just want it to print the name of the path that was given to it.&lt;/p&gt;

&lt;p&gt;But there is a problem, when we execute this code, the same error like before does occur.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# pytest output
FAILED testing.py::test_read_file - FileNotFoundError: [Errno 2] No such file or directory: 'test_file_foo'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So what went wrong here? Did we do something wrong? Is monkey-patching just a scheme which does not really work?&lt;/p&gt;

&lt;p&gt;Turns out, the problem is what (or better where) we have patched. To understand that, we should take a look at this part of the &lt;a href="https://docs.python.org/3/library/unittest.mock.html#where-to-patch" rel="noopener noreferrer"&gt;unittest.mock documentation&lt;/a&gt; which is linked in the pytest documentation:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;patch() works by (temporarily) changing the object that a name points to with another one. There can be many names pointing to any individual object, so for patching to work you must ensure that you patch the name used by the system under test.&lt;/p&gt;

&lt;p&gt;The basic principle is that you patch where an object is looked up, which is not necessarily the same place as where it is defined.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If we look again at the content of &lt;code&gt;utils.py&lt;/code&gt; we see that &lt;code&gt;Path&lt;/code&gt; is also imported there. So in order for the patch to work, you have to also patch it there.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# testing.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;mp_test.utils&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;read_file&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;mp_test&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_read_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;monkeypatch&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;monkeypatch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mp_test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utils&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Path&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;read_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;test_file&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code leads to some progress ... a different error message.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# pytest output
--------------------- Captured stdout call ------------------
test_file_foo
========== short test summary info ================
FAILED testing.py::test_read_file - AttributeError: 'NoneType' object has no attribute 'open'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This error is also to be expected because we replaced &lt;code&gt;Path&lt;/code&gt; with the &lt;code&gt;print&lt;/code&gt; function. That is the reason why we can see the file path in the output.&lt;br&gt;
A consequence is that &lt;code&gt;name_path&lt;/code&gt; is set to &lt;code&gt;None&lt;/code&gt; because that is the return value of &lt;code&gt;print&lt;/code&gt;. This leads to the above error, when the code tries to call the &lt;code&gt;open&lt;/code&gt; function of &lt;code&gt;name_path&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now that we know how to replace functions,  we can go to the next step.&lt;/p&gt;
&lt;h2&gt;
  
  
  Cooking up mock objects
&lt;/h2&gt;

&lt;p&gt;Our goal is to create a &lt;code&gt;MockPath&lt;/code&gt; class which replaces the real &lt;code&gt;Path&lt;/code&gt; and this mock should just return the path name when the &lt;code&gt;open&lt;/code&gt; method is invoked.&lt;br&gt;
So let's try something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# testing.py
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;mp_test&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;mp_test.utils&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;read_file&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MockPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_read_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;monkeypatch&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;monkeypatch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mp_test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utils&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Path&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MockPath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;read_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;test_file&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;This is a step in the right direction but unfortunately it gives us a - somewhat - cryptic error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FAILED mp_test/testing.py::test_read_file - TypeError: 'str' object does not support the context manager protocol
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But a closer look at &lt;code&gt;utils.py&lt;/code&gt; reveals the problem.&lt;/p&gt;

&lt;p&gt;The output of &lt;code&gt;Path.open&lt;/code&gt; is used as a context manager which has to provide the &lt;code&gt;read&lt;/code&gt; method.&lt;/p&gt;

&lt;p&gt;This means that we need another mock which has the methods &lt;code&gt;__enter__&lt;/code&gt;  and &lt;code&gt;__exit__&lt;/code&gt; in order to define a context and a method &lt;code&gt;read&lt;/code&gt; which returns the filename. So we will end up with something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# testing.py
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;mp_test&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;mp_test.utils&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;read_file&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MockReader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__enter__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__exit__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;pass&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MockPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;MockReader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;MockReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_read_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;monkeypatch&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;monkeypatch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mp_test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utils&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Path&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MockPath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;read_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;test_file&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;13&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We have added the &lt;code&gt;MockReader&lt;/code&gt; class which can be initialized with some &lt;code&gt;content&lt;/code&gt; and when calling the  &lt;code&gt;open&lt;/code&gt; method, it returns said content. Upon entering, it returns itself and it does nothing upon exit.&lt;/p&gt;

&lt;p&gt;With these mocks in place, our test actually succeeds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finishing touches
&lt;/h2&gt;

&lt;p&gt;There is actually - at least - one problem with the test setup. Can you spot it?&lt;/p&gt;

&lt;p&gt;The way it is approached now, we are not checking whether &lt;code&gt;Path&lt;/code&gt; is invoked with the correct path. So we might end up in a situation where not the right file is loaded.&lt;/p&gt;

&lt;p&gt;In order to test that, we need a way to see with which arguments &lt;code&gt;MockPath&lt;/code&gt; has been initialized. One possible way to do that would be to use &lt;a href="https://docs.python.org/3/tutorial/classes.html#class-and-instance-variables" rel="noopener noreferrer"&gt;class variables&lt;/a&gt; which could look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# testing.py
&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MockPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;paths&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;paths&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;MockReader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;MockReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_read_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;monkeypatch&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;monkeypatch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mp_test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utils&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Path&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MockPath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;read_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;test_file&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;13&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;test_file_foo&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;MockPath&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;paths&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  The shortcomings of the example
&lt;/h2&gt;

&lt;p&gt;Let me be honest with you. Instead of writing unit tests like this, I would not have written &lt;code&gt;create_file&lt;/code&gt; like this to begin with. Instead, I took it, because it is an easy to understand example.&lt;/p&gt;

&lt;p&gt;Usually, I try to avoid having functions that mix logic - determining the name, processing the content - with the code that gets data from an interface. Instead, I like putting all logic into isolated functions that take "primitive" Python data types as input. This makes testing really easy.&lt;/p&gt;

&lt;p&gt;But there is a middle ground between those two extremes and that would be using abstraction.&lt;/p&gt;

&lt;p&gt;For example, one could do the following thing in &lt;code&gt;utils.py&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# utils.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Reader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mode&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__enter__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__exit__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ReaderContext&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Reader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;read_file&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="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;complete_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;_foo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;complete_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Some things have changed here. Let's take a closer look.&lt;/p&gt;

&lt;p&gt;The biggest change is that we now have two classes &lt;code&gt;Reader&lt;/code&gt; and &lt;code&gt;ReaderContext&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;The naming is questionable, I know, please ignore that for now and focus on what is happening. The &lt;code&gt;ReaderContext&lt;/code&gt; is just a wrapper around the &lt;code&gt;Reader&lt;/code&gt;class which is again a wrapper around &lt;code&gt;Path.open&lt;/code&gt;. Also note that the signature of the &lt;code&gt;read_file&lt;/code&gt; function has changed. A context is now passed in which is called.&lt;/p&gt;

&lt;p&gt;All in all, this should seem familiar to you because it is very similar to the setup of our Mock objects. &lt;br&gt;
These objects already where an abstraction of the functionality that we need to execute &lt;code&gt;read_file&lt;/code&gt;. In the code above we are only wrapping the &lt;code&gt;Path.open&lt;/code&gt; method in this abstraction.&lt;/p&gt;

&lt;p&gt;Now we can also simplify our test, because we no longer need to mock anything. Instead we can pass our mock classes directly to &lt;code&gt;read_file&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# testing.py
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_read_file&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;read_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;test_file&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MockPath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;13&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;test_file_foo&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;MockPath&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;paths&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One last comment, the code could probably be simplified to the point where only one class is needed instead of two, but I intentionally mirrored what was already there in the test. So it is easier to understand. Feel free to simplify the code yourself as an exercise. &lt;/p&gt;

&lt;h2&gt;
  
  
  Next steps
&lt;/h2&gt;

&lt;p&gt;There is one benefit that the more complex implementation brings with it and that is the fact that we are now in a position where we could write other &lt;code&gt;Reader&lt;/code&gt; and &lt;code&gt;ReaderContext&lt;/code&gt; classes that can handle different data sources.&lt;/p&gt;

&lt;p&gt;It would be cleaner to define &lt;a href="https://docs.python.org/3/library/abc.html" rel="noopener noreferrer"&gt;abstract base classes&lt;/a&gt; for both and derive every other class from those. But this is outside of the scope of what I wanted to achieve with this article, so I will leave that for another time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I hope that you found this short introduction to monkey-patching in your tests helpful. The focus was to touch upon different concepts without going too deep on any on them.&lt;/p&gt;

&lt;p&gt;Feedback is always appreciated and let me know whether you would like to read more about certain topics in the future.&lt;/p&gt;

</description>
      <category>python</category>
      <category>testing</category>
      <category>pytest</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Level up your pip install skills</title>
      <dc:creator>topjer</dc:creator>
      <pubDate>Mon, 12 Aug 2024 07:08:15 +0000</pubDate>
      <link>https://forem.com/topjer/all-you-need-to-know-on-how-to-install-things-with-pip-37md</link>
      <guid>https://forem.com/topjer/all-you-need-to-know-on-how-to-install-things-with-pip-37md</guid>
      <description>&lt;p&gt;In this article we are looking at different ways to install code into your virtual environment with pip.&lt;/p&gt;

&lt;p&gt;These will increase in complexity, but fret not, I am there for you every step of the way. &lt;em&gt;pats your back&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Enough talk! Let's start with something easy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installing a local repository
&lt;/h2&gt;

&lt;p&gt;Assume the following situation: You just checked out a repository and want to install the requirements.&lt;/p&gt;

&lt;p&gt;This can easily be done by using the following command ... of course after you have created a virtual environment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ python -m venv (name of virtual environment)
$ source (name of virtual environment)/bin/activate
$ pip install .
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you are wondering about the install command and the absence of a &lt;code&gt;requirements.txt&lt;/code&gt;, I have bad news for you. It is 2024 and you should no longer use a &lt;code&gt;requirements.txt&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is of course only my own opinion, but all repositories I am working with have a &lt;code&gt;pyproject.toml&lt;/code&gt; and I would strongly recommend to use one in every of your projects as well. Viable exception might be sandbox projects and small scripts.&lt;/p&gt;

&lt;p&gt;The "why" would be misplaced here, but allow me to give you a sneak peek. Not only does it allow you to define your requirements themselves. You can also define optional dependencies the user can install if needed.&lt;/p&gt;

&lt;p&gt;This is especially useful for development tools, that you do not want in the productive application, like testing libraries or formatters.&lt;/p&gt;

&lt;p&gt;But this is only the beginning of the list of features. They are also a place for metadata and allow for custom entry points to your application.&lt;/p&gt;

&lt;p&gt;Here the install command again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ pip install .
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make sure that you are in the folder where the &lt;code&gt;pyproject.toml&lt;/code&gt; can be found.&lt;br&gt;
Here a pro-tip, use the following command if you intend to work on the repository you are installing&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ pip install -e .
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This performs an editable install also known as "Development Mode" which allows you to iteratively test your code changes without the need to reinstall your project.&lt;/p&gt;

&lt;p&gt;What does that mean? &lt;/p&gt;

&lt;p&gt;Did it ever happen to you that you changed code in a module you are importing from, but the changes do not seem to take effect? &lt;/p&gt;

&lt;p&gt;Try editable installs!&lt;/p&gt;

&lt;h2&gt;
  
  
  Interlude: Installing from a branch
&lt;/h2&gt;

&lt;p&gt;Quick question: What do you do, if you want to install the version of a specific branch instead of the default branch?&lt;/p&gt;

&lt;p&gt;The answer is obvious&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ git checkout (branch name)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and repeat the steps above! Right?&lt;/p&gt;

&lt;p&gt;You fool, you just activated my trap card!&lt;/p&gt;

&lt;p&gt;See, ever since version 2.23 there is a new kid in town which allows for more intuitive switching of branches and its name is &lt;code&gt;git switch&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;git checkout&lt;/code&gt; has been deprecated ever since.&lt;/p&gt;

&lt;p&gt;So do not expose yourself in front of you colleagues by using out-dated tools. Instead, casually drop a &lt;code&gt;git switch&lt;/code&gt; next time you are sharing your screen to let everyone know that you mean business.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install from a private repository
&lt;/h2&gt;

&lt;p&gt;Brace yourself! &lt;/p&gt;

&lt;p&gt;Everything up to this point was mere child's play. Now it is time for some big boy pip usage.&lt;/p&gt;

&lt;p&gt;See, everyone can install packages available in a package repository, but only knowing how to install from there will mean that all the gold in private repositories will remain inaccessible to you.&lt;/p&gt;

&lt;p&gt;It is also helpful to test your own code before turning it into a package.&lt;/p&gt;

&lt;p&gt;If you ever find yourself in such a situation, use this command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ pip install git+ssh://git@(your provider)/(owner)/(repo name).git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here an example without the placeholders, that might make it easier to understand.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ pip install git+ssh://git@github.com/pandas-dev/pandas.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fun fact: everything after the '://' is almost identical to the ssh command generated by git. But notice, instead of the colon used to separate 'github.com' and the owner 'pandas-dev' a slash has to be replaced.&lt;/p&gt;

&lt;p&gt;What if you want to install from a branch ... or any other reference for that matter?&lt;/p&gt;

&lt;p&gt;Easy! Just add a &lt;code&gt;@(ref)&lt;/code&gt; at the end of the command. So it can look something like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ pip install git+ssh://git@github.com/pandas-dev/pandas.git@1.5.x (branch)
$ pip install git+ssh://git@github.com/pandas-dev/pandas.git@v2.2.2 (tag)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Private Repos and the pyproject.toml
&lt;/h2&gt;

&lt;p&gt;But what if it is not enough to install packages from the command line? What if also your build pipeline should install from a private repository? &lt;/p&gt;

&lt;p&gt;Hopefully, you agree that adding individual &lt;code&gt;pip install&lt;/code&gt; statements to your pipeline is out of the question.&lt;/p&gt;

&lt;p&gt;So instead, let me show you what to add to the dependencies section of the &lt;code&gt;pyproject.toml&lt;/code&gt;.  You will see, that it is very similar to the previous command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"pandas@git+ssh://git@github.com/pandas-dev/pandas.git@1.5.x",
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this added, run again &lt;code&gt;pip install -e .&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Congrats! You have just installed an outdated version of pandas into your environment. You might want to repeat that with the actual package you need.&lt;/p&gt;

&lt;h2&gt;
  
  
  Now make it fast
&lt;/h2&gt;

&lt;p&gt;Since you stuck with me until this point, I will throw in a bonus tool recommendation.&lt;/p&gt;

&lt;p&gt;These past couple of months I have used &lt;a href="https://astral.sh/blog/uv" rel="noopener noreferrer"&gt;uv&lt;/a&gt;, which is a drop-in replacement for pip (among other often used tools in the python eco system) written in Rust.&lt;/p&gt;

&lt;p&gt;The biggest selling point is that it significantly speeds up the creation of virtual environments and the installation of packages. Especially if you are recreating virtual environments, because it uses caching. We are talking about being 10 times faster ... or even 100 times faster if the cache is warm.&lt;/p&gt;

&lt;p&gt;The list of benefits is much longer than that, but also this is a thing for another article. So, give it a try for now and thank me later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Let's wrap this thing up.&lt;/p&gt;

&lt;p&gt;These were all the ways I have used during my work when it comes to the interplay between git and pip. There might be other ways to install stuff, but this should cover 99% of use-cases.&lt;/p&gt;

&lt;p&gt;Have I forgotten your favorite command line trick? Then share it in the comments.&lt;/p&gt;

&lt;p&gt;I hope you learned something new with this article and if you are interested in more technical articles about Software Development, then consider a follow.&lt;/p&gt;

</description>
      <category>python</category>
      <category>git</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Why you should care about Poetry</title>
      <dc:creator>topjer</dc:creator>
      <pubDate>Mon, 26 Feb 2024 21:52:18 +0000</pubDate>
      <link>https://forem.com/topjer/why-you-should-care-about-poetry-1g60</link>
      <guid>https://forem.com/topjer/why-you-should-care-about-poetry-1g60</guid>
      <description>&lt;p&gt;Let me be frank. Python dependency management with pip kinda is a pain.&lt;/p&gt;

&lt;p&gt;I never found it too outstanding, but once I have experienced dependency management with ... (make sure the inner voice reading you this text takes on a loving tone for the next word) &lt;a href="https://doc.rust-lang.org/cargo/" rel="noopener noreferrer"&gt;Cargo&lt;/a&gt; ... (back to normal) something had to change.&lt;/p&gt;

&lt;p&gt;Before we talk about this change, let's first review what had to change.&lt;/p&gt;

&lt;h2&gt;
  
  
  Forgetting dependencies
&lt;/h2&gt;

&lt;p&gt;The following situation happens way to often to me.&lt;/p&gt;

&lt;p&gt;I check out a repository from the company github, navigate to the file containing dependencies, let pip do its magic. Only to face an error message indicating a missing dependency once I try to run the code.&lt;/p&gt;

&lt;p&gt;How the hell could that happen?&lt;/p&gt;

&lt;p&gt;But all is not yet lost. I see what is missing, right? The solution is to simply pip install the missing library, right? Right?&lt;/p&gt;

&lt;p&gt;Wrong!&lt;/p&gt;

&lt;p&gt;See, what I just installed was the latest version of said dependency. But the code was written based on an older version of said dependency that saw several API breaking changes in the meantime.&lt;/p&gt;

&lt;p&gt;Driven by anger and frustration, I open up the commit history of the repository to see who pushed the last changes ... only to find that it was me...&lt;/p&gt;

&lt;h2&gt;
  
  
  Activating the virtual environment
&lt;/h2&gt;

&lt;p&gt;Attentive readers might have noticed that some steps were missing in the last section.&lt;/p&gt;

&lt;p&gt;Of course, you should create a virtual environment in which you install all requirements. Please, do not install everything globally, as that would be the direct path into dependency hell.&lt;/p&gt;

&lt;p&gt;In the off-chance that you are unfamiliar with virtual environments, they are - as the name would imply - a dedicated Python environment, that neither interferes with your global environment nor with other virtual environments.&lt;/p&gt;

&lt;p&gt;They are super useful and should be a trusted tool for all Python developers.&lt;/p&gt;

&lt;p&gt;But their isolating super powers are worth nothing if you forget to actually activate it. Something that can quickly happen if you are in a hurry and especially if your shell does not indicate activated environments. Trust me, happened to all of us!&lt;/p&gt;

&lt;h2&gt;
  
  
  How Poetry alleviates these issues
&lt;/h2&gt;

&lt;p&gt;For installing dependencies, you can use the command &lt;code&gt;poetry add {name of dependency}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Like pip, this installs the dependency. But unlike pip, it automatically installs it into a dedicated virtual environment for the current project. The goodness does not stop there. On top of it, it also adds the dependency to your pyproject.toml.&lt;/p&gt;

&lt;p&gt;You can even &lt;a href="https://python-poetry.org/docs/dependency-specification/" rel="noopener noreferrer"&gt;constraint the allowed versions of the dependency&lt;/a&gt; with powerful tools.&lt;/p&gt;

&lt;p&gt;This can be achieved with &lt;br&gt;
&lt;code&gt;poetry add {name of dependency}@{constraint}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;In order to enable full repeatability of builds, it creates and maintains a lock file that contains the exact version numbers of your current configuration.&lt;/p&gt;

&lt;p&gt;The section up to now already contains a hint at how it solve the second problem: By fully automating the handling of virtual environments. No longer do you need to activate it. You don't even have to remember to create it. Poetry does all of that for you.&lt;/p&gt;

&lt;p&gt;Poetry keeps track which environment was used for which project and uses it whenever necessary.&lt;/p&gt;

&lt;p&gt;The best thing about it, you can even have multiple environments for the same project. Gone are the days where you have to ask yourself: "Was env1 the environment with Python 3.9 or was it env2?"&lt;/p&gt;

&lt;p&gt;Thus, in the end, all you need to do is type &lt;code&gt;poetry run python {file name}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;There is also no need to create an environment for a repository you just checked out. Simply use &lt;code&gt;poetry install&lt;/code&gt; and all the needed requirements are being installed into an environment that is also being created for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I do not want to drag this one out any further as this article was intended to be an appetizer.&lt;/p&gt;

&lt;p&gt;By now you are armed with some basic commands and hopefully some curiosity to try it out for yourself. So head on over to the &lt;a href="https://python-poetry.org/docs/#installation" rel="noopener noreferrer"&gt;installation page&lt;/a&gt; and you can be the proud owner of your very own copy of poetry.&lt;/p&gt;

&lt;p&gt;I hope you learned something in this article! Feedback is always appreciated and see you in the next one!&lt;/p&gt;

</description>
      <category>python</category>
      <category>tutorial</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Don't make these mistakes in your CV</title>
      <dc:creator>topjer</dc:creator>
      <pubDate>Mon, 21 Nov 2022 11:33:21 +0000</pubDate>
      <link>https://forem.com/topjer/reading-cvs-is-suffering-3ijh</link>
      <guid>https://forem.com/topjer/reading-cvs-is-suffering-3ijh</guid>
      <description>&lt;p&gt;Ah yes, another year is slowly coming to an end. This means that project plans are nearing fruition/failure. Maybe people start to feel the call for change, thinking to themselves that 2023 will be the year when they leave the hell hole they wander daily.&lt;/p&gt;

&lt;p&gt;Or maybe their company is bought by a eccentric billionaire who decides to lay of half of his software engineering team based on the &lt;a href="https://twitter.com/parismarx/status/1587966900516147200" rel="noopener noreferrer"&gt;lines of code written in the last year&lt;/a&gt; - sick metric by the way!&lt;/p&gt;

&lt;p&gt;Whatever the reason might be, we get a ton of applications at my company. Since I am one of the most senior member of my team I get the honor of screening all those CVs together with my boss. A process that turned out to be surprisingly painful.&lt;/p&gt;

&lt;p&gt;So I thought I share some of that with you. Mostly to entertain. But who knows? Maybe there will even be an insight in here.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Context
&lt;/h2&gt;

&lt;p&gt;Allow me to give you some context. &lt;/p&gt;

&lt;p&gt;My Company is based in Germany. So some things I complain about might not be relevant in other countries like the US.&lt;/p&gt;

&lt;p&gt;Also, the position we are trying to fill was the one of a Senior Python Developer. So, we wanted to quickly identify people that can show relevant experience and invite them to an interview.&lt;/p&gt;

&lt;p&gt;As it turns out, this is not that easy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Brevity Is The Soul of Wit
&lt;/h2&gt;

&lt;p&gt;Let me start by saying this: I don't like reading applications! It keeps me from doing my actual work.&lt;/p&gt;

&lt;p&gt;There is clearly the need for it. Our workload is increasing and we can always use some fresh meat. Yet, I get mildly irritated when I have to read a CV for more than 5 minutes.&lt;/p&gt;

&lt;p&gt;Again, all I need is the faint impressions that you have done some Python in the last 3 years. &lt;em&gt;BAM&lt;/em&gt; Your are in the next round!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A CV is not there to get you hired. It is there to get you invited to an interview.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Yet, what I was getting were applications that meticulously wrote about the projects they have been involved in. Pages over pages. In the worst cases it was not even apparent what their part has been in all of that.&lt;/p&gt;

&lt;p&gt;Some I topped that, because it was not already enough to read. they Also added an evaluation from their current employer. Two pages of prose about what a great employee they are written in font size 8. At least this is what I suppose it said because I did not bother reading it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You want to seem professional? Then respect the time of whoever is going to read you application.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The Other End of the Spectrum
&lt;/h2&gt;

&lt;p&gt;There were also CVs that were drastically different. &lt;/p&gt;

&lt;p&gt;One guy, fresh from university applied to the position and sent a one page CV that only contained the information that he just recently finished his Bachelor Thesis.&lt;/p&gt;

&lt;p&gt;The infuriating thing here is not that he applied to a Senior Positions with hardly any work experience. This could have been due to the fact that our Recruiter actually was the first to approach. No, it was infuriating because there was nothing the read from this CV.&lt;/p&gt;

&lt;p&gt;"If the CVs are long it is bad but if they are short it is just as bad. Seems like this guy is never happy" might be what you are now thinking. So allow me to elaborate what I was looking for in a CV.&lt;/p&gt;

&lt;h2&gt;
  
  
  Your Past Experiences
&lt;/h2&gt;

&lt;p&gt;If you want to apply for developer position using language X then make it clear why you are the right one for it based on your past experiences. Highlight the problems you solved with X, which libraries you have used etc.&lt;/p&gt;

&lt;p&gt;Also, be more detailed in recent jobs and become more coarse with jobs that are longer in the past. It is not really convincing when the last relevant job experience was 15 years ago.&lt;/p&gt;

&lt;p&gt;This is even applicable if you are fresh from university. You might not have any work experience but there hopefully are some coding projects you can name where you have used X.&lt;/p&gt;

&lt;p&gt;Now that we have established what I would like to see, let's continue with some things I did not like to see.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tell Me Something About You
&lt;/h2&gt;

&lt;p&gt;Here in Germany it is common practice to end you CV with some words about yourself.&lt;/p&gt;

&lt;p&gt;By that I literally mean that a couple of activities are listed, people do in their free time. I assume the idea here is to give an impression of the person behind the CV.&lt;/p&gt;

&lt;p&gt;Yet it seems rather futile to compress something as complex as a humans nature into 5 nouns.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Well, this CV does not contain any relevant work experience but the fact that she likes to perform traditional polish dance is intriguing. We should definitely give her a chance.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And please, for gods sake. If you are thinking about adding 'Watching Netflix' to it ...&lt;/p&gt;

&lt;p&gt;Don't!&lt;/p&gt;

&lt;p&gt;Really, I mean it.&lt;/p&gt;

&lt;p&gt;Which scenario are you imagining where this would make you more attractive as an employee?&lt;/p&gt;

&lt;p&gt;&lt;em&gt;the screen gets blurry and the next thing we see is the Head of a software engineering team lost in deep contemplation&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;"I'll be damned. Before me sits the best team of software engineers the world has ever seen. Each one of them has their own area of expertise which they complement with a broad overview other topics.&lt;/p&gt;

&lt;p&gt;They are living and breathing software development. Each waking hour is spent either writing, reading or thinking about code.&lt;/p&gt;

&lt;p&gt;Heck, some of them even learned lucid dreaming so they can keep solving engineering problems while they dream.&lt;/p&gt;

&lt;p&gt;But not a single one of them can give me a recommendation what to watch on Netflix. I am getting sick and tired of rewatching The IT Crowd."&lt;/p&gt;

&lt;p&gt;He is torn from his thoughts by the sound of trumpets echoing from above. Confused as to what could be the origin of these otherworldly melodies, he approaches the window.&lt;/p&gt;

&lt;p&gt;What he sees is that the clouds are parting. His eyes squint as he adjusts to the heavenly glow. Finally he can make out a shape. It is the hand of the creator of all things reaching out for him.&lt;/p&gt;

&lt;p&gt;To an outside observer this scene would be reminiscent to "The creation of Adam".&lt;/p&gt;

&lt;p&gt;But unlike in the picture, the hand is holding something. Something divine. Something to end his monotonous binges.&lt;/p&gt;

&lt;p&gt;It is your CV.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Snap, back to reality.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If this is the scenario you are hoping for, then good luck with that.&lt;/p&gt;

&lt;h2&gt;
  
  
  I Really Want to Work at {insert name here}
&lt;/h2&gt;

&lt;p&gt;Not sure whether this is a thing outside of Germany. But here we usually get a letter alongside the CV that allows the applicant to give a more detailed insight into reasoning for the application. (This is changing somewhat, yet often times we still get them)&lt;/p&gt;

&lt;p&gt;Most letters I have seen share two qualities.&lt;/p&gt;

&lt;p&gt;On the one hand, they are a joy to read. They eloquently explain the applicants motivation. Why your company is the ideal workplace and how thrilled they would be to get a chance to work alongside you.&lt;/p&gt;

&lt;p&gt;Sometimes these letters are so poignant that I am filled with a deep gratitude. Because I am actually working for this wonderful company.&lt;/p&gt;

&lt;p&gt;If only it weren't for the second quality: Being written so generically that it could fit any company out there.&lt;/p&gt;

&lt;p&gt;I do understand that it is unrealistic to write a unique letter for each company you are applying to. Still, if you decide to add a cover letter, then make at least one paragraph unique to the company. Refer to something found in the job description for example.&lt;/p&gt;

&lt;p&gt;Yet, even better - although it will put a tear to the eyes of all those cover letter ghost writers out there - do not bother with it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Worst is yet to come
&lt;/h2&gt;

&lt;p&gt;You know what I really despise about reading CVs?&lt;/p&gt;

&lt;p&gt;It is when people are judging their skill set themselves. There even seem to be websites out there that create these overviews for you.&lt;/p&gt;

&lt;p&gt;Just choose the technologies you are familiar with and then indicate your skill level on a scale from 0 to whatever.&lt;/p&gt;

&lt;p&gt;Aside from the fact that they sometimes are so horribly formatted (font and color scheme) that I dread just looking at them. They are also absolute BS.&lt;/p&gt;

&lt;p&gt;There are probably enough studies out there indicating that we are not capable of objectively judging our skills. With a clear tendency towards overestimating the own proficiency.&lt;/p&gt;

&lt;p&gt;Thus these overviews are only a groundless claim. Worthless without backup.&lt;/p&gt;

&lt;p&gt;If, on the other hand, I see that they have been working as a Python developer for the past five years. Explicitly naming - some of - the projects they have been working on, together with the libraries / technologies used. It becomes a more reliable basis to judge the skill.&lt;/p&gt;

&lt;p&gt;Bottom line: Ditch these overviews. They are just noise (and make my eyes bleed).&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In case you had as little patience with this article as I was having with some CVs and skipped right to the end, I want to repeat the main point.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Past experience is key.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;List all your past experiences that are relevant for the position you want to apply to. Be succinct and precise and cut out all the noise.&lt;/p&gt;

&lt;p&gt;The interviews are the place where you can shine with your unique personality (and your Netflix recommendations).&lt;/p&gt;

&lt;p&gt;Let me know in the comment section how you approach the topic of a CV or maybe you are in a similar position and have read some CVs in the past. Then feel free to share your opinion as well.&lt;/p&gt;

</description>
      <category>tutorial</category>
    </item>
    <item>
      <title>I saved money, time (and the environment) with Python</title>
      <dc:creator>topjer</dc:creator>
      <pubDate>Tue, 01 Nov 2022 21:24:45 +0000</pubDate>
      <link>https://forem.com/topjer/i-saved-money-time-and-the-environment-with-python-fic</link>
      <guid>https://forem.com/topjer/i-saved-money-time-and-the-environment-with-python-fic</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Admittedly, the title of this article is ambitious. So allow me to explain.&lt;/p&gt;

&lt;p&gt;Not too long ago, I have started a subscription for some workout program. My plan was to watch the program on my phone while being in the gym.&lt;/p&gt;

&lt;p&gt;It soon turned out that this program was merely a collection of YouTube videos. The ones you can only access when you have the link.&lt;br&gt;
Paying 50+ Euro for that every month, by the way.&lt;/p&gt;

&lt;p&gt;The scrooge in me came up with an idea.&lt;/p&gt;

&lt;p&gt;"Why not download these videos and cancel the subscription?"&lt;/p&gt;

&lt;p&gt;So, I downloaded an App to my phone and started the dirty work. But here comes the catch: Since I did not want to spend money for this App, I went with a free version.&lt;/p&gt;

&lt;p&gt;This came with its own set of problems. It forced me to watch a ton of adds. Adds when I downloaded a video. Adds when I wanted to watch a video.&lt;/p&gt;

&lt;p&gt;Now, the programmer in me came up with an idea.&lt;/p&gt;

&lt;p&gt;"Should be possible to download these videos with some Python code."&lt;/p&gt;

&lt;p&gt;And so it began.&lt;/p&gt;
&lt;h2&gt;
  
  
  Let's talk about morale
&lt;/h2&gt;

&lt;p&gt;I won't hide the fact that I feel a tiny bit of guilt for downloading the videos.&lt;/p&gt;

&lt;p&gt;My justification is that I think that the product provided does not work well as a subscription model.&lt;/p&gt;

&lt;p&gt;It just feels wrong to pay 50 bucks every month to access an existing library of YouTube videos with a handful of new additions every month. YouTube videos which you could probably still access without the subscription if you store the IDs somewhere.&lt;/p&gt;

&lt;p&gt;In a desperate attempt to divert the blame, I would even say that it is their own fault for making a product that you can simply download.&lt;br&gt;
It's like leaving your valuables lying around in your front yard.&lt;/p&gt;

&lt;p&gt;This diversion works only as long as no one asks why I did not just search a different product if this one is not worth the money for me. To that I can only answer:&lt;/p&gt;

&lt;p&gt;Because I like to eat my cake and keep it!&lt;/p&gt;

&lt;p&gt;With all that being said and nothing gained, dearest reader, just forget about all of this and get distracted by the shiny things in the rest of this article.&lt;/p&gt;
&lt;h2&gt;
  
  
  Back to the title
&lt;/h2&gt;

&lt;p&gt;Let's address the points in the title individually.&lt;/p&gt;

&lt;p&gt;It should be obvious how I have saved money. I no longer had to pay for the subscription.&lt;/p&gt;

&lt;p&gt;This way I would miss out on the new content, but I could come back after a few months, download the new stuff and quit again.&lt;/p&gt;

&lt;p&gt;Because, I really know no shame.&lt;/p&gt;

&lt;p&gt;Also, I have a limited amount of mobile data. Since I do not have to upgrade that in order to watch videos I save again money. Admittedly a bit far fetched, I know.&lt;/p&gt;

&lt;p&gt;How was time saved?&lt;/p&gt;

&lt;p&gt;Well, since I was watching these videos on my phone, I would have to endure Ads. Either in YouTube or in the App used for downloading. Not anymore, Sir!&lt;/p&gt;

&lt;p&gt;Time lost with buffering issues will also be saved on top of that.&lt;/p&gt;

&lt;p&gt;But how in the hell did I save the environment?&lt;/p&gt;

&lt;p&gt;My thought process on this is simple: There are videos I like which I henceforth watch multiple times. If I were to&lt;br&gt;
stream that video multiple times, servers would have to provide that to me and the transmission infrastructure would have to transport that to me.&lt;/p&gt;

&lt;p&gt;This requires electricity and leaves a carbon foot print. If it is avoided, the environment is saved (a tiny bit).&lt;/p&gt;

&lt;p&gt;With a high likelihood my thought process is too simplistic and studies are a bit vague about the &lt;a href="https://www.iea.org/commentaries/the-carbon-footprint-of-streaming-video-fact-checking-the-headlines" rel="noopener noreferrer"&gt;actual impact streaming has on the environment&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So ... did I mention the environment in the title in order to get more attention? The answer to that is a clear ... maybe. But since we are already here, let's talk about ...&lt;/p&gt;
&lt;h2&gt;
  
  
  The code
&lt;/h2&gt;

&lt;p&gt;Three major problems had to be solved:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How can I access the website of the product?&lt;/li&gt;
&lt;li&gt;How can I extract the video IDs from said website?&lt;/li&gt;
&lt;li&gt;How can I download these videos?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To sum it up quickly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The first problem was solved with the &lt;a href="https://requests.readthedocs.io/en/latest/" rel="noopener noreferrer"&gt;requests package&lt;/a&gt; and providing the active session credentials as cookies and headers.&lt;/li&gt;
&lt;li&gt;The second problem was solved with the &lt;a href="https://www.crummy.com/software/BeautifulSoup/bs4/doc/" rel="noopener noreferrer"&gt;BeautifulSoup4 package&lt;/a&gt; to perform some - crude - html parsing.&lt;/li&gt;
&lt;li&gt;The last problem was solved with &lt;a href="https://pytube.io/en/latest/" rel="noopener noreferrer"&gt;pytube&lt;/a&gt; which makes downloading YouTube videos a breeze.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In hopes that you are not some kind of coding savant that already connected all the dots, you will find the details in the following sections.&lt;/p&gt;
&lt;h2&gt;
  
  
  How To Get In
&lt;/h2&gt;

&lt;p&gt;This is not my first rodeo, I have scraped websites before. This usually did the trick for me:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;awesome&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;https://topjer.net&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Firing the piece of code usually resulted in a return code of 200 - huge success - and full access to the code of the website.&lt;/p&gt;

&lt;p&gt;Yet the website required authentication. Instead of a shiny 200, I got a gloomy 403. So I did what every developer would do ... I went over to stackoverflow.&lt;/p&gt;

&lt;p&gt;Io and behold, I found &lt;a href="https://stackoverflow.com/questions/23102833/how-to-scrape-a-website-which-requires-login-using-python-and-beautifulsoup" rel="noopener noreferrer"&gt;this&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The solution I ended up using was to navigate to the network tab in the developer tools of my browser after logging in and extracting the request as a cURL. This can be copied to &lt;a href="https://curlconverter.com/" rel="noopener noreferrer"&gt;https://curlconverter.com/&lt;/a&gt; and it will create the python code needed to access the site.&lt;/p&gt;

&lt;p&gt;Now the code looks something like this, with COOKIES and HEADERS taken straight from the before mentioned website.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CURRENT_URL&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/index&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cookies&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;COOKIES&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;HEADERS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These are the solution I like. They work first try and don't require me to understand what the hell is even going on.&lt;/p&gt;

&lt;p&gt;Am I concerned because I have entered potentially sensitive information into some random website?&lt;/p&gt;

&lt;p&gt;No, you dummy! Because the Privacy Note on the website promises that everything happens in my browser and no information is sent anywhere.&lt;/p&gt;

&lt;p&gt;Why would I be distrustful if a site promises me my data is safe?&lt;/p&gt;

&lt;h2&gt;
  
  
  The extraction
&lt;/h2&gt;

&lt;p&gt;On to the next step. What we have right now is the pure html code, so full of pointy '&amp;gt;' and '&amp;lt;' that I am afraid I might hurt myself if I stare at it for too long.&lt;/p&gt;

&lt;p&gt;What to do? Like in real life: if you take something dangerous and cook it for some time, it becomes less dangerous ... and maybe delicious in the process.&lt;/p&gt;

&lt;p&gt;Terrible analogy, but all for the sake of transitioning to the Python library I have been using: BeautifulSoup.&lt;/p&gt;

&lt;p&gt;According to the website:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Beautiful Soup is a Python library for pulling data out of HTML and XML files. It works with your favorite parser to provide idiomatic ways of navigating, searching, and modifying the parse tree. It commonly saves programmers hours or days of work.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;My highly sophisticated workflow looks like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Load the website in my browser.&lt;/li&gt;
&lt;li&gt;Right click the object I am interested in and choose 'Inspect'.&lt;/li&gt;
&lt;li&gt;With the element highlighted in the html code of the website, I try to identify parents with hopefully unique tags to make my way down to the element.&lt;/li&gt;
&lt;li&gt;Rinse and repeat if necessary.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is the structure I had to navigate in my case:&lt;/p&gt;

&lt;p&gt;The starting page had several drop down menus on it. The third one contained entries that would lead me to the site with the videos I wanted.&lt;/p&gt;

&lt;p&gt;So here is what I had to do:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Find all drop down menus and get the content of the third one.&lt;/li&gt;
&lt;li&gt;Extract all links from said drop down menu.&lt;/li&gt;
&lt;li&gt;Loop over the list of links and in every instance find the embedded YouTube video and extract the ID.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Lets have a look at some code snippets. Note that this is not the actual code I have used. It merely is intended to show the functionalities I have been working with.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;bs4&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BeautifulSoup&lt;/span&gt;

&lt;span class="n"&gt;soup&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BeautifulSoup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;html_mess&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;html.parser&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;dropdown_menus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;soup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;div&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;class_&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;col-xs-12&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;findAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;li&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;class_&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;dropdown&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;movement_menu&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dropdown_menus&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;entries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;movement_menu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;tag&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;li&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;data-post_id&lt;/span&gt;&lt;span class="sh"&gt;'&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;entry&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;video_source_id_raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;data-video_source_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also, since this is not an in depth guide about BeautifulSoup, I will just briefly explain what the purpose of my code is. I claim by no means that it is the most efficient way of doing it.&lt;/p&gt;

&lt;p&gt;The first step is to parse the html string, which in my case comes from 'response.content'.&lt;/p&gt;

&lt;p&gt;In order to find the tags I am looking for I mainly use the 'find' and 'findAll' functions. Mind blowing, I know.&lt;br&gt;
If only the first tag that matches a description is needed, use 'find'. If all instances are of interest, use 'findAll' (or 'find_all').&lt;/p&gt;

&lt;p&gt;The first argument you must provide is the type of the tag, i.e. the first thing that comes after the '&amp;lt;' and then whichever combination of attributes is fitting.&lt;/p&gt;

&lt;p&gt;In my case it was sufficient to look for the class attribute of the tag.&lt;/p&gt;

&lt;p&gt;But it is also possible to provide a function that returns True or False. In my case I use lambda functions which check for the tag to be 'li' and whether the tag has an attribute 'data-post_id'.&lt;/p&gt;

&lt;p&gt;For me personally, it was easier to work with lambda functions and I also assume that they give a greater flexibility. But I might as well be wrong with that statement...&lt;/p&gt;

&lt;p&gt;Note that the result of the find function is again a Soup object, so multiple consecutive searches can be executed.&lt;/p&gt;

&lt;p&gt;In case you want to go through the list of direct children of the tag, i.e. all top-level tags within said tag, use the 'contents' attribute.&lt;/p&gt;

&lt;p&gt;If you want to access the value of a given attribute, then you can do that as if the soup object were a dictionary, as I do it in the last line.&lt;/p&gt;

&lt;p&gt;And with this, I want to conclude the section about how I extracted the video IDs. If you want to know more about html parsing with BeautifulSoup, then check out the tutorial, which is excellent. Or let me know in the comment section and I might go more 'in depth' on it.&lt;/p&gt;
&lt;h1&gt;
  
  
  Securing the Goods
&lt;/h1&gt;

&lt;p&gt;After all this heavy lifting is done, we can finally get to the interesting part. Downloading the videos themselves. A task that is pretty easy due to the hard work of other people.&lt;/p&gt;

&lt;p&gt;There is this package called 'Pytube' which takes care of everything. Here is all the code you need:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pytube&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;YouTube&lt;/span&gt;

&lt;span class="n"&gt;video&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;YouTube&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;https://www.youtube.com/watch?v=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;video_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;stream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;streams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_extension&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;mp4&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resolution&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;720p&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;progressive&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;_&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.mp4&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;file_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;download&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;output_path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;./downloads/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First, you 'connect' to the video by supplying its url.&lt;/p&gt;

&lt;p&gt;Then you can get the list of all available streams via 'video.stream'.&lt;/p&gt;

&lt;p&gt;This list can be filtered by varying attributes like the file extension or resolution.&lt;/p&gt;

&lt;p&gt;One comment to the attribute 'progressive=True'. If you check a video, you will notice that most have 'progressive=False'. Which means that for most streams audio and video are separated. In that case you have to download video and audio separately and combine them afterwards.&lt;/p&gt;

&lt;p&gt;Because I am lazy, I hope for a stream that has 'progressive=True'.&lt;/p&gt;

&lt;p&gt;Then I choose the first result I get, remove those pesky spaces from the title and proceed to downloading the video.&lt;/p&gt;

&lt;p&gt;Sadly, by default, you do not get much feedback on the current progress of the download. The code simply halts execution until the download is done.&lt;br&gt;
It seems like you could provide a callback function upon progress of the download and thus get some insight on how much is left to download. But I have not looked into that much.&lt;/p&gt;
&lt;h2&gt;
  
  
  Added bonus: Download music
&lt;/h2&gt;

&lt;p&gt;Since we are here, I might as well mention that slight modifications of the above code will allow you to only download the audio track of a video. Ideal if you want to download some music.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;video&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;YouTube&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;https://www.youtube.com/watch?v=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;video_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;_&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.webm&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;file_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;streams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mime_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;audio/webm&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;last&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;download&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All you need to do is change the filter. In my examples it was most reliable to check for the 'mime_type' and since mp3 was not always available, I used 'webm'.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Now you should have all the pieces to glue together your own tool to extract youtube video ids from a website and then download them.&lt;/p&gt;

&lt;p&gt;I'd love to hear your feedback in the comment section below.&lt;/p&gt;

</description>
      <category>python</category>
      <category>tutorial</category>
      <category>sustainable</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Arbitrage - this is not a Game</title>
      <dc:creator>topjer</dc:creator>
      <pubDate>Wed, 22 Jun 2022 15:28:31 +0000</pubDate>
      <link>https://forem.com/topjer/arbitrage-this-is-not-a-game-386e</link>
      <guid>https://forem.com/topjer/arbitrage-this-is-not-a-game-386e</guid>
      <description>&lt;p&gt;This article will describe how I created and implemented a trading model for a game I have recently started playing. Even though it is "just a game" and therefore "not real". It is still a great sandbox to play around with, to explore concepts and to improve coding skills.&lt;/p&gt;

&lt;p&gt;I will begin with describing the market conditions and drawing conclusions from then. The next step will be to dissect the first model I have created and lastly I will describe all the flaws and how to improve.&lt;/p&gt;

&lt;h1&gt;
  
  
  It All Started With a Game
&lt;/h1&gt;

&lt;p&gt;Some months ago I have started to play Final Fantasy 14, an massively multiplayer online role playing game (MMO) set in the popular Final Fantasy universe.&lt;br&gt;
As is usually the case for MMOs, this game has an auction house where you can buy and sell in-game items for in-game currency.&lt;/p&gt;

&lt;p&gt;Whilst this is probably only a side feature for most people, it can be a great source of virtual income if played right.&lt;/p&gt;

&lt;h1&gt;
  
  
  The Market
&lt;/h1&gt;

&lt;h2&gt;
  
  
  The Facts
&lt;/h2&gt;

&lt;p&gt;Let us first gather the hard facts of the market as they set the boundaries to the model we do need.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The in-game currency is called 'Gil'.&lt;/li&gt;
&lt;li&gt;The markets are called 'Market Boards'.&lt;/li&gt;
&lt;li&gt;The game has so called 'Data Centers' which are, well, data centers in their
respective region. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every data center has multiple worlds and you can travel without much effort between those worlds. (It only takes a couple of seconds to load and you do not have to pay anything.)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The market boards between the worlds of a data center are not connected. That means that prices can evolve differently between the worlds depending on the supply and demand generated by their player base.&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A character can only sell items on the market board of their home world, i.e. the world they have been created on but can buy from every world of their data center.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If you want to sell an item you must place a sell order, specifying the price per   item and the number of items you want to sell as a stack. Stack size is limited to 99.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Buy orders are not available! If you want to buy an item you have to choose among the existing sell orders.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Partially buying a sell order is not possible. You must buy the whole position.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Selling incurs a fee of 5%. As a matter of fact it could be reduced to 3% but for the sake of simplicity we will work with 5%, as this will not change the general approach.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Buying an item only comes with a fee of 5%, if you do that on a world that is not   your home world.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It is not possible to directly access market data, but there are websites that have users collect and upload price data. For example &lt;a href="https://universalis.app/" rel="noopener noreferrer"&gt;Universalis&lt;/a&gt;, which handily provides an API we can use to query order book and sales data. (As a side note, there actually seems to be an API for the market data in the game but it is not publicly accessible.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Trading happens 24/7.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Implications
&lt;/h2&gt;

&lt;p&gt;Now it is time to draw a couple conclusions from the facts above. Again, let's do this in the form of bullet points.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Because the market boards of different worlds are not connected, cross worlds arbitrage opportunities can arise.&lt;/li&gt;
&lt;li&gt;Ideally you would want to match buy orders of your home world with sell orders from a foreign world and execute immediately. But since you can only create a sell order on your home world it might happen that the price drops rapidly, leaving you at a loss.
This means there is a certain risk associated to the chosen approach, so technically it might not be fully correct to talk about 'arbitrage'. Yet I will continue to do so, as it makes it sound way cooler.&lt;/li&gt;
&lt;li&gt;Not being able to partially execute an order is also bothersome. On the one hand you might not be able to afford a lucrative position. On the other hand, it forces us to think about good stack size when selling ourselves.&lt;/li&gt;
&lt;li&gt;Because we cannot access up to date date, it can happen that arbitrage opportunities we find are no longer available.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Model v0.1
&lt;/h2&gt;

&lt;p&gt;Here is the high level work flow of what my first model was doing.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Query a list of items that have last been updated on a given world. There is an &lt;a href="https://docs.universalis.app/#most-recently-updated-items" rel="noopener noreferrer"&gt;API endpoint&lt;/a&gt; for that, provided by Universalis.&lt;/li&gt;
&lt;li&gt;For each item I would determine the minimum price on each world of my data center by again querying the &lt;a href="https://docs.universalis.app/#market-board-current-data" rel="noopener noreferrer"&gt;API from Universalis&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Compute the difference between those prices from other worlds to the price on my home world and remember all servers where the price difference is positive, i.e. where I can make money.&lt;/li&gt;
&lt;li&gt;Query the list of past transactions (another call to the &lt;a href="https://docs.universalis.app/#market-board-sale-history" rel="noopener noreferrer"&gt;Universalis API&lt;/a&gt;) and filter for the last 14 days. Compute the total number of traded items per day. Afterwards, compute the max, min and median over the available days.&lt;/li&gt;
&lt;li&gt;As a measure for how profitable an item could be, I computed the 'median win' which  is the computed price difference times the median number of traded items.&lt;/li&gt;
&lt;li&gt;In the end I would report those items that have

&lt;ul&gt;
&lt;li&gt;a profitable price difference&lt;/li&gt;
&lt;li&gt;trading activity on 85% of days in the past 14 days.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Shortcomings
&lt;/h2&gt;

&lt;p&gt;Every journey has to start with a first step. The same is true in this case.&lt;br&gt;
It will probably not surprise you, that this model did not work too well. But it allowed for exploration of basic concepts and fundamental implementation techniques.&lt;/p&gt;

&lt;p&gt;Enough talk! Here are the issues that quickly arose alongside with some possible improvements for the future.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The 'median win' was incredibly misleading. It would usually overestimate the potential win, obviously due to its poor design.

&lt;ul&gt;
&lt;li&gt;It could be that only a single position would be priced much cheaper than the rest. So the same price difference could not be achieved with the other positions.&lt;/li&gt;
&lt;li&gt;Alternatively, it could happen that the number of favorable positions is less than the median.&lt;/li&gt;
&lt;li&gt;It would be much better to compare the actual listings of other worlds with the minimum price on my home world. That way I could see the profit to be made.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Turns out that picking the items 'at random' is not the best approach. What a Shocker! That way it is hard to find potential arbitrage in general and even if something is found, it might be in a category that has little demand.

&lt;ul&gt;
&lt;li&gt;It would be much more promising to focus on categories with higher demand and thus higher trading activity. The margins might be smaller in this segment but being able to quickly close a position reduces risk. The longer a position is open, the higher the chance that the prices fall.&lt;/li&gt;
&lt;li&gt;Going by category requires to prepare the mapping of items to categories. This can be achieved as this data is freely available.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;I was ignoring the fees entirely.

&lt;ul&gt;
&lt;li&gt;Luckily, the impact of this is not too bad. It just slightly overestimates profit margins. On top of that, it is easy to fix. Simply add the fees to the formula for the price differences. So easy that it makes you wonder why it was not implemented in the first place.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;My script had poor error handling that would result in it crashing for items that are iliquid.

&lt;ul&gt;
&lt;li&gt;Mostly annoying and nothing that couldn't be fixed quickly.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Working with absolute prices differences. Expensive items would produces higher price differences but would also carry a greater risk / require a greater initial investment.
A price difference of 5000 sounds like much but when the item costs 7 mio Gil, then it might not be worth the effort.

&lt;ul&gt;
&lt;li&gt;This can be fixed by putting the win in relation to the required investment.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;It was hard to decide on stack size for my sell orders. I had an idea of the daily volume but not which stack size I should choose.

&lt;ul&gt;
&lt;li&gt;This is a tricky one. I have noticed tendencies for some items, e.g. people would pay more for a smaller stack when they need less of those items.&lt;/li&gt;
&lt;li&gt;One possible approach could be to look at the histogram of past stack sizes but I have the feeling that there is much more to uncover here.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;The last one is less of a shortcoming but contains a lot of potential: There seem to be fluctuations in the price of items depending on the weekday. Which can be attested to the fact that more people play on the weekends. Yet the impact of that is not easy to predict. Maybe this means that the demand is increased because everyone needs a certain item or -conversely- the supply increases because more is being produced.

&lt;ul&gt;
&lt;li&gt;The big issue here is data availability, as Universalis does not seem to keep a complete sale history. Thus it would be necessary to create a price time series yourself and then analyze the price change with regards to the week day.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h1&gt;
  
  
  Implementation
&lt;/h1&gt;

&lt;p&gt;In the end, I want to briefly touch upon how I implemented this script without going into too much detail.&lt;br&gt;
Everything is implemented in Python and is run under Windows Subsystems for Linux on my Windows gaming PC. This makes me chuckle a bit every time because I assume that I am the only one with a bash window on his second screen while playing.&lt;/p&gt;

&lt;p&gt;The API requests are done with the 'requests' package and the most frequently needed calls are encapsulated in functions.&lt;/p&gt;

&lt;p&gt;Data wrangling is done with 'Pandas' which gave me quite a lot of head ache because of how unfamiliar I am with the package. Also because I had clear ideas how to achieve what I want as a set of SQL statements.&lt;br&gt;
This was especially annoying in conjunction with dates and date time operations.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;The project so far had its ups and downs.&lt;/p&gt;

&lt;p&gt;The implementation was rather frustrating at times. Especially because I knew clearly how to solve the problems with other tools. As if that was not enough, the outcome turned out to be less usable in the end then I had hoped for.&lt;/p&gt;

&lt;p&gt;But how do they say: 'It is about the journey and not the destination'. It was really fun to think about ways to find arbitrage and the first version of the model made it really easy to come up with improvements.&lt;/p&gt;

&lt;p&gt;The issues with the implementation were also very educational and now I have a first impression of pandas. I would probably still have to google everything, if I were to repeat the implementation. But maybe I would be a bit quicker in the process.&lt;/p&gt;

&lt;p&gt;I definitely want to continue with this project as it holds a ton of potential for further learnings even outside of the things mentioned here and I will definitely take you along on the journey.&lt;/p&gt;

&lt;p&gt;Do you have other ideas how to improve my model or did you realize arbitrage possibilities somewhere else? Let me know in the comments.&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>datascience</category>
      <category>watercooler</category>
      <category>devjournal</category>
    </item>
    <item>
      <title>What is docker and why should you care?</title>
      <dc:creator>topjer</dc:creator>
      <pubDate>Sat, 14 May 2022 17:12:58 +0000</pubDate>
      <link>https://forem.com/topjer/what-is-docker-and-why-should-you-care-3lc4</link>
      <guid>https://forem.com/topjer/what-is-docker-and-why-should-you-care-3lc4</guid>
      <description>&lt;p&gt;At my work it manifests itself that our future lies with docker containers. As a matter of fact even more confusing terms are being used. They talk about ‘CRC’ aka ‘Code Ready Containers’, Kubernetes clusters and other things that make my head spin so violently that I have to hold onto my chair, else I would topple over.&lt;/p&gt;

&lt;p&gt;In case it is not clear already, I am not in the slightest familiar with these technologies. But this is no reason to fret! It is my believe that the IT sector means constant learning and so I welcome this step towards ‘modern’ tools. (In an industry that is evolving as quickly as it does one should always be careful when using the word modern.)&lt;br&gt;
Especially, since my company has a tendency to lack behind – significantly – when it comes to technology.&lt;/p&gt;

&lt;p&gt;So I started to look into Docker containers and first tried to answer the question:&lt;/p&gt;

&lt;h2&gt;
  
  
  “What are they and why should I care”.
&lt;/h2&gt;

&lt;p&gt;Let’s have a look at the bigger picture by looking at where we are coming from.&lt;/p&gt;

&lt;h3&gt;
  
  
  The simple beginnings
&lt;/h3&gt;

&lt;p&gt;In the past things were much simpler. Every server had exactly one operating system installed. Let it be a version of Windows (Server) or a flavor of Linux. Everything would be controlled by one OS.&lt;/p&gt;

&lt;p&gt;This simplicity would come with a lack of flexibility. What if you have Linux installed but need Windows for an application or vice versa? What if you need more computational power or less? Would you want to invest in new hardware and have to maintain it in that case?&lt;/p&gt;

&lt;p&gt;The issues do not stop there. On application level there are further issues when you have just one big server. What if you have conflicting dependencies between the software you use? Maybe your WordPress and your data base cannot come to terms about drivers they need.&lt;/p&gt;

&lt;h3&gt;
  
  
  Divide and conquer
&lt;/h3&gt;

&lt;p&gt;And with that, the stage opens for virtual machines. They use something called a hypervisor to divide a computers resource for multiple guest operating systems. They come in two different flavors. Either they function as a lightweight operating system and run directly on the hardware or they run on a host operating system.&lt;/p&gt;

&lt;p&gt;All of a sudden, you are able to quickly react to changing needs. You can create a new virtual machine on your existing hardware that runs Windows Server or Linux. If one machine needs more power, you can simply assign more resources (if available), no need to get new hardware.&lt;/p&gt;

&lt;p&gt;In a sense, Virtual Machines are virtualizing hardware.&lt;/p&gt;

&lt;h3&gt;
  
  
  One step back and a giant leap forward
&lt;/h3&gt;

&lt;p&gt;Docker now goes one step further … or maybe back. You no longer need a hypervisor.&lt;br&gt;
Instead a single operating system on your hardware suffices. Because what docker is doing is to virtualize the operating system instead of the hardware.&lt;/p&gt;

&lt;p&gt;The docker engine runs on the host operating system and hosts itself so called docker “containers”. Which could be described as isolated micro computers. They can have their own CPU, OS, Memory and Network.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why care?
&lt;/h2&gt;

&lt;p&gt;Their strongest selling point is that they are lightweight. Once you have the Docker engine installed you for example pull the docker image for Ubuntu – which is currently only 77mb in size – and you have it running within seconds.&lt;/p&gt;

&lt;p&gt;Installing Ubuntu on a virtual machine takes magnitudes longer.&lt;/p&gt;

&lt;p&gt;But how is that even possible?&lt;/p&gt;

&lt;p&gt;The secret is that Linux based docker containers share their Kernel with the host operating system. This brings one big drawback with it and that is that a Linux OS can only host Linux docker container whereas a Windows host can only run Windows docker container. (Not taking into account that you could use Windows Subsystems for Linux).&lt;/p&gt;

&lt;p&gt;The biggest advantage is yet to come. Technically I have already mentioned it, but I want to emphasize it …strongly! They are isolated and therefore highly portable.&lt;/p&gt;

&lt;p&gt;A scene like the following happens every day around the globe and is with a high probability also familiar to you:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Developer: Finally I am done with this feature. All tests are green. Time to give it up for review.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;other Developer / Tester tries to run the code and it fails&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Developer: Don’t know the problem. IT RUNS ON MY MACHINE.&lt;/em&gt; &lt;/p&gt;

&lt;p&gt;Who knows how much money / time is lost because of differing runtime environments. May it be the environment variable that you have changed months ago or this one dependency that you forgot to document. Maybe you run a different version of insert about anything or who knows what might be the reason.&lt;/p&gt;

&lt;p&gt;This will be a thing of the past. Because everything you need for a script to run must be part of a docker image. When it runs on your machine, it will run on every other machine. Because all dependencies and all those little tweaks must be part of the docker image.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Quick break to explain terminology: an image is the abstract definition of the environment and&lt;br&gt;
every instance of this image is called a docker container. Cool? Cool!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You wanna know what else is neat? It’s this thing about isolation. You can have different parts of your server run in their own docker container and have those containers communicate with one another. Each having their needed dependencies in the version they need.&lt;/p&gt;

&lt;p&gt;One component needs an update either for itself or a dependency? No problem. Your other components will not be affected. They might not even notice anything being different.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;It’s time to wrap this up. This article was only intended to be a brief introduction into the topic of docker and as such can only be seen as a starting point.&lt;/p&gt;

&lt;p&gt;Most information stem from this &lt;a href="https://www.youtube.com/watch?v=eGz9DS-aIeY" rel="noopener noreferrer"&gt;NetworkChuck video&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Check it out if you want to see some Docker containers in action.&lt;/p&gt;

&lt;p&gt;In case you jumped right to the end in order to find out why you should care. Here are the 3 main reasons I see:&lt;/p&gt;

&lt;p&gt;Docker containers are&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;lightweight&lt;/li&gt;
&lt;li&gt;isolated&lt;/li&gt;
&lt;li&gt;highly portable&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devops</category>
      <category>docker</category>
      <category>beginners</category>
    </item>
    <item>
      <title>6 crypto investment mistakes I made so you don't have to</title>
      <dc:creator>topjer</dc:creator>
      <pubDate>Sat, 07 Aug 2021 15:44:32 +0000</pubDate>
      <link>https://forem.com/topjer/6-crypto-investment-mistakes-i-made-so-you-don-t-have-to-1m98</link>
      <guid>https://forem.com/topjer/6-crypto-investment-mistakes-i-made-so-you-don-t-have-to-1m98</guid>
      <description>&lt;p&gt;The crypto world is exciting. Filled to the brim with technological possibilities unimaginable mere 10 years ago. As a result it becomes harder and harder not to dip your toe into these waters if you have even a remote interest in technology. This is at least how it was for me.&lt;/p&gt;

&lt;p&gt;In the spirit of full disclosure I want to say that I find the whole conglomerate of DeFi, CeFi, Dex, blockchain and whatnot more than confusing. It happens rather regularly that I read an article about crypto which I simply do not understand.&lt;br&gt;The incredible opportunities come at a price of great complexity and this great complexity leads to one thing especially in the beginning ...&lt;/p&gt;

&lt;p&gt;Mistakes.&lt;/p&gt;

&lt;p&gt;Thus I want to give you an overview over the mistakes that I have done or just barely gotten away from.&lt;/p&gt;

&lt;h1&gt;Intransparency of value&lt;/h1&gt;

&lt;p&gt;If you delve deeper the matter of crypto and are not just in it to hold some Bitcoin then you might encounter a situation that is similar to the following:&lt;/p&gt;

&lt;blockquote&gt;&lt;p&gt;Good, my 50 Euro have arrived at Binance. First I will buy some BNB. &lt;em&gt;*buys BNB&lt;/em&gt;*&lt;br&gt;Now I want to transfer that to my wallet. *&lt;em&gt;accepts fees and transfers it&lt;/em&gt;*&lt;br&gt;Sweet, next step: I buy some sweet sweet CAKE over at PancakeSwap. '&lt;em&gt;buys CAKE&lt;/em&gt;'&lt;br&gt;Wait, you can by lottery tickets with CAKE? Gimme five! *&lt;em&gt;buys five tickets&lt;/em&gt;*&lt;br&gt;I always wanted to hold billions of coins. Time to buy some OSM *&lt;em&gt;buys a ridiculous amount of OSM&lt;/em&gt;*&lt;/p&gt;&lt;/blockquote&gt;

&lt;p&gt;After this shopping spree you end up with a bunch of numbers in your wallet. My personally, I rather quickly forget the value these numbers actually hold.&lt;/p&gt;

&lt;p&gt;Is a transaction fee of 0.01 BNB much?&lt;br&gt;How much did I really lose when I spend 5 CAKE on lottery tickets and not win anything?&lt;/p&gt;

&lt;p&gt;I especially noticed that I was sometimes reckless with spending crypto because it was so intangible. Lottery tickets for 0.3 CAKE? Count me in! An NFT for 400 KSM? Interesting! (Not an actual investment of mine)&lt;/p&gt;

&lt;p&gt;So my tip here would be the following: Whenever you want to spent some crypto, first ask yourself how much money that actually is. Once you know that, ask yourself whether you really want to go through with the transaction. This is especially true for transactions where you loose your coins, e.g. because you have to pay fees compared to transactions where you gain an equal amount of another coin.&lt;/p&gt;

&lt;h1&gt;Underestimate transaction fees&lt;/h1&gt;

&lt;p&gt;I want to stress the last point because I fell for that trap too often in the short time that I buy crypto. The worst thing is that you do not even immediately notice that you have fallen for it.&lt;br&gt;This is closely connected to the Intransparency of value I have mentioned before. Even though Meta Mask shows you the maximum gas fees, it can be hard to understand what it means.&lt;/p&gt;

&lt;p&gt;This totally leaves aside the fact that it is easy to confuse a 0.0008 with 0.008 and thus you do not even notice that you spent a 10 times higher fee for a transaction.&lt;/p&gt;

&lt;p&gt;Time for a story. It was the first time I traded crypto outside the safe haven that is a broker. I was ready for the wide world that is crypto and started with &lt;a href="https://pancakeswap.finance/" rel="noopener noreferrer"&gt;PancakeSwap&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For some time everything was smooth sailing. I could provide liquidity, I could invest in Syrup pools. Life was filled with syrup-like goodness.&lt;/p&gt;

&lt;p&gt;But dark clouds were forming on the horizon. PancakeSwap has a mechanic where you can claim a bounty which activates the compounding function for the Auto pool. For this interaction you of course have to pay a small fee but simply wait for a bounty that is higher than the gas fees you have to pay. Problem solved&lt;br&gt;Nothing is as tempting as the promise of free money so I tried to claim the bounty … and failed.&lt;/p&gt;

&lt;p&gt;As a side note, it can be really hard or virtually impossible to understand why a transaction failed. Especially in the beginning. In hindsight I think the problem was that Meta Mask was calculating the gas fees incorrectly or the bounty has already been claimed.&lt;/p&gt;

&lt;p&gt;So I did the most natural thing to do in this situation. I kept on trying over and over again. Every time I got the same outcome.&lt;br&gt;At one point I decided to stop. Not because I came too my senses but I made a cruel discovery. For each failed transaction I had to pay gas fees!&lt;/p&gt;

&lt;p&gt;Here an explanation why you have to pay for failed transactions on the ethereum network. I assume&lt;br&gt;that similar arguments also apply to other networks:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The Ethereum network requires gas to execute transactions. When you send tokens, interact with a contract, send ETH, or do anything else on the blockchain, you must pay for that computation. That payment is calculated in gas, and gas is always paid in ETH.&lt;/p&gt;
&lt;p&gt;You are paying for the computation, regardless of whether your transaction succeeds or fails. Even if it fails, the miners must validate and execute your transaction, which takes computational power. You must pay for that computation, just like you would pay for a successful transaction.&lt;br&gt;&lt;/p&gt;
&lt;cite&gt;&lt;p&gt;https://metamask.zendesk.com/hc/en-us/articles/360045439051-Why-did-I-pay-gas-fees-for-a-failed-transaction-&lt;/p&gt;&lt;/cite&gt;
&lt;/blockquote&gt;

&lt;p&gt;In the end I had 6 failed transactions. The most expensive one costed me 0.69$. This is pretty high considering that I was trying to claim a bounty of 0.25$.&lt;/p&gt;

&lt;p&gt;So always make sure you know how much you are paying for gas and if the transaction is not time sensitive then try to postpone it to another time with potentially better gas prices.&lt;/p&gt;

&lt;h1&gt;Unaware of "The others"&lt;/h1&gt;

&lt;p&gt;The internet is full of stories of people who made a ton of money. One smart investment. Getting into this one crypto that no one saw coming early. BOOM! Big returns. Big numbers. Incredible riches.&lt;/p&gt;

&lt;p&gt;If it is not the story of people "having made it" then it is the promise that there is this one particular coin that will blow up soon. It is sooooo good it is only a question of time until it goes to the moon. &lt;br&gt;Side note: People advertising a coin are usually heavily invested in that coin themselves and would therefore profit of other people buying it as well. &lt;/p&gt;

&lt;p&gt;In your head you are already thinking about where the perfect place for the new TV is that you will buy once you have become crypto rich as well. &lt;br&gt;But before you drift too deeply into this crypto dream I want to tell you something about the survivorship bias .&lt;/p&gt;

&lt;p&gt;Let us start with with its definition, it is&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;the logical error of concentrating on the people or things that made it past some selection process and overlooking those that did not, typically because of their lack of visibility. &lt;/p&gt;
&lt;cite&gt;&lt;p&gt;https://en.wikipedia.org/wiki/Survivorship_bias&lt;/p&gt;&lt;/cite&gt;
&lt;/blockquote&gt;

&lt;p&gt;People that lost most of their investment because they put it into "the next big thing" will probably not brag about it on the internet. Maybe because they are ashamed. &lt;/p&gt;

&lt;p&gt;"People will think I am dumb and not as smart as that other guy" might be a thought that is creeping up in their head. &lt;br&gt;Or they are simply too angry and want nothing to have to do with this whole crypto thing.&lt;br&gt;Lastly, you probably do not see these posts because they do not sell as well. They are not getting upvoted and&lt;br&gt;people who run a coin will hardly advertise the post of someone who lost everything with their coin.&lt;/p&gt;

&lt;p&gt;So for starters I would advise you to keep in mind that for everyone who made money with a coin there&lt;br&gt;must be someone who lost money with that coin. This is of course somewhat of an oversimplification but the core holds true.&lt;/p&gt;

&lt;p&gt;We can also address this from another angle. If you have already some investment experience then ask yourself how many of your losses you have talked about on the internet. I would assume that the answer is at least 'Not each one' and probably more along the lines of 'None'.&lt;/p&gt;

&lt;p&gt;A more realistic view would now be to assume this to be true for everyone you come across. Everyone has some investment skeletons in the closet and rich people usually have big closets ...&lt;/p&gt;

&lt;h1&gt;Get rich quick mentality&lt;/h1&gt;

&lt;p&gt;This is an extension of the last point. So lets say that you are aware that people lose money. Not everyone&lt;br&gt;can be a winner. Still you could be one if you just approached it right.&lt;/p&gt;

&lt;p&gt;How could you not want big numbers and prestige for yourself. So you start looking at dirt cheap coins with&lt;br&gt;zero utility and think: "Maybe this is it. Maybe if I invest just a tiny bit then it would not be too bad if the coins goes to zero. Yet it has potential to blow up really big."&lt;/p&gt;

&lt;p&gt;Or you become part of a "pump and dump" community. The people are very nice and they guarantee success. They have done that many times before and everyone in there swears they have been making money.&lt;/p&gt;

&lt;p&gt;For those of you who do not know what those communities do. They coordinate buying a certain coin. This results in an increase in price. Maybe they also spread the news like "XYZ is totally blowing up. You must get in now!"&lt;br&gt;Either way, other people will start noticing the increase in price and will buy as well. That is the moment when the community starts selling the coin to these new buyers for a higher price. Making a profit in the process (and letting those new buyers plunge into their doom).&lt;/p&gt;

&lt;p&gt;In case you are wondering: Yes, this is market manipulation and would have severe consequences in other markets like the stock market for example. But good thing crypto is mostly unregulated. Am I right?&lt;br&gt;Just hope that the community you are in does not have an inner circle. An elitist group that has bought the coin before and then is dumping their coins onto you. Making you the poor fool plunging into his doom.&lt;/p&gt;

&lt;p&gt;I think the point I am trying to make is that a get-rich-quick-mentality makes you vulnerable to scams. Stay away from riskier investments. Even if people claim there is zero risk involved understand this: you get a higher return on your investment the more risk you are willing to take. A high yielding investment without risk does not exist. Here I would like to ignore arbitrage situation for the moment which would be return at zero risk but that is a very special situation.&lt;/p&gt;

&lt;p&gt;Here is my recommendation: In the beginning buy one of those established coins and hold it for a while. Get a feeling for the ups and downs of the crypto market. You can invest in shit coins. But only amounts that you can stomach loosing. Also do not spend all your time researching the next coin that is blowing up. Instead learn more about crypto. Because this world is actually quite fascinating once you look past making quick profit.&lt;/p&gt;

&lt;h1&gt;It always pays to take a second look&lt;/h1&gt;

&lt;p&gt;Let me make that crystal clear: Always make sure you are sending your crypto to the correct adress!&lt;/p&gt;

&lt;p&gt;Picture this: you want to send a noteworthy amount of crypto from one wallet to the other. You have done that before. No big deal!&lt;br&gt;You click send in one wallet and then you wait. But your crypto does not arrive in the other wallet. Nervousness is rising. You decide to check the transaction which luckily you can because a block chain essentially is a vault made out of glass. Comparing the target address from the transaction with you intended target address you realize that one digit is false. Do you know what that means?&lt;/p&gt;

&lt;p&gt;You crypto is gone! With a high probability will this wallet not belong to anyone. Even if it would belong to someone it would be nigh impossible to contact that person.&lt;/p&gt;

&lt;p&gt;I am very grateful that this situation did not yet happen to me. Once I almost thought that I lost my money when trying to transfer SHIB from Binance to my wallet. In the end it turned out that I did not add the correct currency to Meta Mask and that was why it did not show my transferred crypto.&lt;/p&gt;

&lt;p&gt;The take away point: If you intend to send a bigger amount of money or if you do your first transaction to a wallet it might be a good a idea to test it with a small transaction first. Alternatively it could be possible to simulate the transaction on the testnet if that is available for you crypto of choice.&lt;/p&gt;

&lt;h1&gt;Nothing but a speck of dust&lt;/h1&gt;

&lt;p&gt;It is wise to start with small amounts when investing into crypto. This helps to get a feeling for how the markets move.&lt;/p&gt;

&lt;p&gt;But it also comes with the own set of problems. The following two points are more of a word-of-warning, things to be aware about and not concrete tips.&lt;/p&gt;

&lt;h2&gt;The transaction is too small compared to the fees&lt;/h2&gt;

&lt;p&gt;For me, everything started with buying 10 Euros worth of Ethereum. Having this money in my Coinbase wallet was exhilarating. It felt like I was holding a piece of future in my hand. &lt;br&gt;That it until I wanted to move that ETH into my MetaMask wallet and saw that I would have to pay 8 Euros of fees. Guess what! Those ETH stayed with Coinbase for a long time.&lt;/p&gt;

&lt;h2&gt;Your holding amount is to small to do what you want to do&lt;/h2&gt;

&lt;p&gt;It happened to me way to often that I wanted to do a certain transaction but could not do it because it required bigger amounts then I was holding.&lt;/p&gt;

&lt;p&gt;Let me give you an example: I have a some money invested in &lt;a href="https://drip.community/" rel="noopener noreferrer"&gt;Drip&lt;/a&gt; where I get 1% per day on my staked amount. Instead of reinvesting my interest, I claimed it accidentally. When I wanted to reinvest it I couldn't because only investments of more than 1 drip are allowed. So now I have 0.16 Drip lying around and I have no idea what to do with it.&lt;/p&gt;

&lt;h1&gt;Conclusion&lt;/h1&gt;

&lt;p&gt;There you have it. These are 6 beginner pitfalls that you hopefully can avoid now that you are aware of them. None of those hurt me too much but it either resulted in burning some money or loosing some hair because of the stress they caused me.&lt;/p&gt;

&lt;p&gt;Feel free to share your beginner mistakes with me in the comments and it case that it was not clear already because I was talking about "mistakes", nothing in this article should be seen as investment advice.&lt;/p&gt;

</description>
      <category>crypto</category>
      <category>beginners</category>
    </item>
    <item>
      <title>My greatest Prank ... that went a little too far</title>
      <dc:creator>topjer</dc:creator>
      <pubDate>Thu, 03 Jun 2021 21:01:09 +0000</pubDate>
      <link>https://forem.com/topjer/my-greatest-prank-that-went-a-little-too-far-39n</link>
      <guid>https://forem.com/topjer/my-greatest-prank-that-went-a-little-too-far-39n</guid>
      <description>&lt;p&gt;At work I try my best to be professional. Or at least to make others think that I am.&lt;/p&gt;

&lt;p&gt;Yet, I was once given the opportunity to pull a masterpiece of a prank on one of my colleagues. Needless to say that I took it.&lt;/p&gt;

&lt;h2&gt;&lt;span&gt;The Setup&lt;/span&gt;&lt;/h2&gt;

&lt;p&gt;It was a normal day at work. I was inspecting the task manager on one of our servers and to&lt;br&gt;my surprise I noticed that I was actually an administrator on said server.&lt;br&gt;&lt;br&gt;Actually it was not too surprising. It was the development server. I am a developer. Getting total freedom to test things is kinda useful and only makes sense. But let us continue&lt;/p&gt;

&lt;p&gt;Curiosity took over and I started exploring this new world of possibilities. First, I noticed&lt;br&gt;that I was able to disconnect coworkers from the server. This is definitely the basis for some good prankery. But I was ultimately drawn towards a function that was a bit more subtle.&lt;/p&gt;

&lt;p&gt;It was possible to send a message to any user logged in to the server.&lt;/p&gt;

&lt;p&gt;I assume this can be used to inform users on a server directly about maintenance work&lt;br&gt;you have to perform. Totally serious stuff.&lt;/p&gt;

&lt;p&gt;But even the most upright tool can be turned into something devilish in the hands of&lt;br&gt;a fiendish mind.&lt;/p&gt;

&lt;p&gt;The functionality also played right into my hands. Not only was I able to freely choose&lt;br&gt;the message I send. Also the header of the message could be modified. Perfect for erasing&lt;br&gt;traces.&lt;/p&gt;

&lt;h2&gt;The Prank&lt;/h2&gt;

&lt;p&gt;The target was quickly acquired. A new colleague with a great interest in Bitcoin. He was talking about that 90% of the time.&lt;/p&gt;

&lt;p&gt;It might sound like bullying to you. Picking on the new colleague. But rest assured that I really liked him personally. So it was more like a prank between friends.&lt;/p&gt;

&lt;p&gt;My message was custom tailored to him with a pinch of ludicrousness known from infamous&lt;br&gt;email scams and topped off with a touch of impending danger.&lt;/p&gt;

&lt;p&gt;But enough talk. Lay your eyes on the masterpiece I created.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--KwDXl6R7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://topjer.net/wp-content/uploads/2021/06/image.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--KwDXl6R7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://topjer.net/wp-content/uploads/2021/06/image.png" alt="" width="742" height="277"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The message was prepared. All that was left to do was to send it. Which I did. Expecting&lt;br&gt;a good laugh between colleagues.&lt;/p&gt;

&lt;p&gt;Little did I know that it would have a far bigger impact.&lt;/p&gt;

&lt;h2&gt;The Fallout&lt;/h2&gt;

&lt;p&gt;I might be a genius trickster but my timing was abysmal. Because I sent my message immediately before went out for lunch. That way I was not able to stop the avalanche that was about to start moving.&lt;/p&gt;

&lt;p&gt;My colleague, understandably bewildered, send an email to all developers. 'All' meaning me and another one.&lt;br&gt;Unfortunately he also took our boss into the loop. Our boss who has a slight tendency for overreaction.&lt;/p&gt;

&lt;p&gt;Fearing the infection of chinese-russian malware he informed the server admins. A complete rollback of the server was in discussion.&lt;/p&gt;

&lt;p&gt;Meanwhile, I was still on lunch break. Chuckling at the thought that I was playing a small prank.&lt;/p&gt;

&lt;p&gt;Luckily my boss got a hold of me before the server rollback plans were turned into reality. He asked me if I have any idea what was going on here. It was easy to play clueless since the conversation took place over teams.&lt;/p&gt;

&lt;p&gt;Unaware of the extend of the consequences of my actions I broke into laughter at some point.&lt;/p&gt;

&lt;p&gt;Needless to say, my boss did not find it funny.&lt;/p&gt;

&lt;p&gt;I got away with a lecture. The admins, exasperated, could be appeased. But I think they still&lt;br&gt;hold it against us.&lt;br&gt;&lt;br&gt;Next time I checked, we developers were no longer admins on the server. Even though&lt;br&gt;that could be due to totally unrelated reasons. That's at least what I would like to believe.&lt;/p&gt;

&lt;p&gt;What about my colleague? After he stomached the shock, we were able to laugh about it. We&lt;br&gt;even now do that from time to time when the memory comes up again.&lt;/p&gt;

&lt;h2&gt;Final Thoughts&lt;/h2&gt;

&lt;p&gt;Would I play a similar prank again if another chance arises?&lt;/p&gt;

&lt;p&gt;Probably not, I have "grown" since then and try to be more of a senior at work.&lt;/p&gt;

&lt;p&gt;Do I regret my actions? &lt;/p&gt;

&lt;p&gt;Not a bit because it was an excellent prank and is the source for some good laughs.&lt;/p&gt;

&lt;p&gt;What about you? Have you done something similar to a coworker? Feel free to share your&lt;br&gt;story in the comments.&lt;/p&gt;

</description>
      <category>officestories</category>
      <category>prank</category>
      <category>watercooler</category>
    </item>
    <item>
      <title>Brave - The Browser that pays Crypto</title>
      <dc:creator>topjer</dc:creator>
      <pubDate>Thu, 06 May 2021 07:03:34 +0000</pubDate>
      <link>https://forem.com/topjer/brave-the-browser-that-pays-crypto-c2n</link>
      <guid>https://forem.com/topjer/brave-the-browser-that-pays-crypto-c2n</guid>
      <description>&lt;p&gt;Recently I read an article about upcoming innovative block chain technologies. What stood out the most to me was the mentioning of a new browser called 'Brave'.&lt;/p&gt;

&lt;p&gt;It has a strong focus on security of your data. But what is truly remarkable is the fact that you can be rewarded BAT when using the browser. This stands for 'Basic Attention Token' and is a cryptocurrency, i.e. it can also be exchanged for real money. &lt;/p&gt;

&lt;p&gt;Yet there is so much more to this browser.&lt;/p&gt;

&lt;h2&gt;Brave takes care of your security&lt;/h2&gt;

&lt;p&gt;You have to make sure that the lock is shown in the address bar of your browser whenever you visit a website. Everyone knows that, right? It indicates the use of https. This ensures the authenticity of the visited website and the protection of the privacy because the exchanged data is encrypted.&lt;br&gt;Brave will upgrade your connection to https whenever possible!&lt;/p&gt;

&lt;p&gt;But it gets even better. It will block all trackers from websites. Online trackers are scripts designed to gather information about how you move through your web.&lt;/p&gt;

&lt;p&gt;The following situation is probably familiar to you: You want to buy something and do some research.&lt;br&gt;Next thing you know is that you see advertisements for this product everywhere.&lt;br&gt;Perfect example of a "successful" tracker.&lt;/p&gt;

&lt;p&gt;If you require more security you can also open a private tab with Tor. This will not only conceal your browsing history. It will also hide your IP address to the sites you are visiting.&lt;/p&gt;

&lt;h2&gt;Brave is faster&lt;/h2&gt;

&lt;p&gt;Brave keeps even more unwanted things out of your surfing experience. It will block any advertisements you encounter. This naturally leads to the side being loaded faster while also saving you bandwidth (which it likes to remind you of).&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%2Ftopjer.net%2Fwp-content%2Fuploads%2F2021%2F05%2Fimage-2.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%2Ftopjer.net%2Fwp-content%2Fuploads%2F2021%2F05%2Fimage-2.png" alt=""&gt;&lt;/a&gt;Number of trackers &amp;amp; ads blocked, bandwidth and time saved.&lt;/p&gt;

&lt;p&gt;Still, the claim of higher speeds should be taken with a grain of salt. It might be true that Brave is faster than for example a normal Firefox browser. Yet, you can install extensions on you Firefox browser like the add blocker which does something similar and thus will also produce similar speed improvements.&lt;/p&gt;

&lt;p&gt;The benefits of Brave here are that you do not have to look for extensions that fulfill your need for security and add-removal. It will come right out of the box. Also, you can rest assured that it will be maintained properly.&lt;/p&gt;

&lt;h2&gt;Brave and Ads&lt;/h2&gt;

&lt;p&gt;Let's talk about what I like most about this browser. No, it is not the fact that you can earn money with it. Well, only kind of. &lt;br&gt;It is the fact that they are "taking out the middle man". What do I mean by that?&lt;/p&gt;

&lt;p&gt;My understanding of the current modus operandi of advertisement in the net is the following:&lt;br&gt;You are the product that is being sold. Websites get money for showing you advertisements. Your surfing behavior is analyzed and the gathered data is being used to show you custom-tailored ads. Whoever gathers this data can make a ton of money with it.&lt;/p&gt;

&lt;p&gt;(I am sure that this an oversimplification. Hey, I never claimed to be an expert on this. So if anyone has more information how advertisement on the web really works, let me know.)&lt;/p&gt;

&lt;p&gt;Brave is different. All ads you would normally encounter. Blocked! All trackers that would analyze your behavior. Blocked!&lt;br&gt;&lt;br&gt;In some sense there is an advertisement void which Brave knows cleverly to fill. It gives you the option to be shown commercials. This happens in a very low key and non intrusive manner. No more "Your PC is infected. Get help now!" banners.&lt;/p&gt;

&lt;p&gt;You have control over the number of ads you are being shown. If you want, you can even&lt;br&gt;turn of that feature entirely and browse completely ad free.&lt;/p&gt;

&lt;p&gt;It should be mentioned that, in order to provide you custom tailored ads, Brave itself does analyze your browsing behavior&lt;br&gt;&lt;br&gt;So how is that better than what others are doing? &lt;br&gt;It is better because this happens locally on your device. The information is - allegedly - not shared with anyone. At least not in a form where it could be connected to your devices or user identity.&lt;/p&gt;

&lt;p&gt;Let's hear what Brave has to say about that:&lt;/p&gt;

&lt;blockquote&gt;&lt;p&gt;We do not have access to identifiable user data. The anonymized aggregated ad campaign related data we do collect is used for accounting and reporting, but this data cannot be mapped back to devices or user identities of any kind.&lt;/p&gt;&lt;/blockquote&gt;

&lt;p&gt;In summary: you are only shown a handful of "high quality" ads at your own volition. On the other side advertisers will get better results because their ads are precisely targeted and they do not have to&lt;br&gt;give a huge chunk of their profit to middlemen.&lt;/p&gt;

&lt;h2&gt;Let's talk about BAT&lt;/h2&gt;

&lt;p&gt;So, what kind of ads to you get you ask? Either an ad is displayed when you open a new tab. Which you might not even notice. Or a box pops up which you might as well could ignore. Whenever this happens you are rewarded with a fraction of a BAT. To let you partake in the advertisement revenue.&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%2Ftopjer.net%2Fwp-content%2Fuploads%2F2021%2F05%2Fimage.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%2Ftopjer.net%2Fwp-content%2Fuploads%2F2021%2F05%2Fimage.png" alt=""&gt;&lt;/a&gt;An example of an ad&lt;/p&gt;

&lt;p&gt;At first I was somewhat confused.&lt;/p&gt;

&lt;blockquote&gt;&lt;p&gt;If I earn BAT by watching ads, does that not mean that evermore BAT is produced and you will have inflation?&lt;/p&gt;&lt;/blockquote&gt;

&lt;p&gt;But I was missing the point. The BAT that is earned is not produced. Instead it has been "bought" by the&lt;br&gt;advertiser and will be given to you as a reward for your attention.&lt;/p&gt;

&lt;p&gt;In total the amount of BAT is limited to 1.5 billion of which 1 billion are in circulation, &lt;a href="https://www.finder.com/how-to-buy-basic-attention-token" rel="noopener noreferrer"&gt;see here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Before we continue, a big disclaimer. My understanding of cryptos is limited. I am fascinated by the topic but did not yet dare to dive in.&lt;/p&gt;

&lt;p&gt;This brings me to the next point I like about Brave.&lt;/p&gt;

&lt;p&gt; It lowers the barrier of entry to the topic crypto currencies. I did not mind changing my main browser (especially since I could easily import all settings and bookmarks without issues) and now I earn crypto while doing something I am very familiar with. Browsing the web.&lt;/p&gt;

&lt;p&gt;The specifics of how to trade or cash in BATs is still nebulous to me. I intend to give you an update on that&lt;br&gt;once I more experience with that. &lt;/p&gt;

&lt;p&gt;But there is another use for the BAT you amass.&lt;/p&gt;

&lt;h2&gt;You can support your favorite content creators with BAT&lt;/h2&gt;

&lt;p&gt;Brave also thought about all those content creators that might miss out on advertisement revenues. You can support them directly with your BAT. Either in the form of a monthly or a one time payment.&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%2Ftopjer.net%2Fwp-content%2Fuploads%2F2021%2F05%2Fimage-1.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%2Ftopjer.net%2Fwp-content%2Fuploads%2F2021%2F05%2Fimage-1.png" alt=""&gt;&lt;/a&gt;Support a verified website. Either with a monthly amount or a one time donation&lt;/p&gt;

&lt;p&gt;You even have the option to let Brave decide whom to give a portion of your BAT based on your browsing&lt;br&gt;history. It determines the fraction of time you spent on every website and splits up an amount of your&lt;br&gt;choosing when it is payday.&lt;/p&gt;

&lt;p&gt;Because you might want to give more BAT to content creators than you can earn in a month it is also possible to load additional funds into you wallet.&lt;/p&gt;

&lt;p&gt;Again, you have fine control over the specifics here and can also choose to ignore this feature entirely.&lt;br&gt;I personally prefer to choose myself whom to give how much.&lt;/p&gt;

&lt;p&gt;The drawback here is that we are still in the early adoption phase of this technology and most content&lt;br&gt;creators are not yet verified. If a creator or website did not verify itself with Brave then your payment will remain in your wallet until they do.&lt;/p&gt;

&lt;h2&gt;Brave is open source&lt;/h2&gt;

&lt;p&gt;Your heared right. The source code for Brave is completely open for public scrutiny. you want to, you can check out the entire source code over at &lt;a href="https://github.com/brave/brave-browser" rel="noopener noreferrer"&gt;Github&lt;/a&gt;. &lt;br&gt;This gives the statement that gathered data stays on your machine more weight. Even though it is more of a head thing because I am not going to check the code to verify it.&lt;/p&gt;

&lt;h2&gt;Brave is not without controversy&lt;/h2&gt;

&lt;p&gt;So far this browser sounds too good to be true. Thus I decided to find out whether there are critiques out&lt;br&gt;there and what they do have to say.&lt;br&gt;Turns out that there is actually some controversy surrounding Brave. I have found two things that happened in recent history.&lt;/p&gt;

&lt;h3&gt;"Hijacking" of referral links&lt;/h3&gt;

&lt;p&gt;When users visited certain cryptocurrency websites the browser would &lt;a href="https://decrypt.co/31522/crypto-brave-browser-redirect" rel="noopener noreferrer"&gt;autocomplete the referral link&lt;/a&gt; of Brave without asking.&lt;br&gt;Some people called that hijacking. But I think that this is not entirely accurate. Hijacking for me would be when you use a referral code which is than replaced by the one of Brave. That was not the case.&lt;br&gt;They were only taking commissions that would be lost otherwise. Which probably does not even hurt the users but the platforms in question. Still I can understand why people got upset.&lt;/p&gt;

&lt;p&gt;After the public outcry this feature was turned off by default. You can activate it if you are cool with it.&lt;/p&gt;

&lt;h3&gt;Intransparency of tips&lt;/h3&gt;

&lt;p&gt;Another controversial topic was tipping content creators that are not yet verified by Brave. Previously the BAT would go into some kind of omnibus account waiting for the creator to register. To some users this seemed like the money was taken by Brave in hopes that it will never be claimed.&lt;/p&gt;

&lt;p&gt;The process was fundamentally changed once this came out. If you are tipping a content creator that is not yet verified then your tip will remain in your wallet until he does.&lt;/p&gt;

&lt;h3&gt;Bottom line&lt;/h3&gt;

&lt;p&gt;I am not entirely sure what to think about these two incidents. In case of the intransparency of tips I'd like to give them the benefit of the doubt. &lt;br&gt;The "hijacking" thing weighs a bit heavier. It was by no means behind the back because the user could clearly see the auto-completion. Yet I cannot deny that I would expect to be asked whether I am fine with that.&lt;br&gt;It shows me that I should use Brave with some vigilance.&lt;/p&gt;

&lt;h2&gt;Final Thoughts&lt;/h2&gt;

&lt;p&gt;My attitude towards Brave - as you might have guessed - is mostly positive. The two mentioned incidents leave a bit of bitter taste in my mouth. But not to such it an extent that it is off putting.&lt;br&gt;I find their approach to advertisement fascinating and their security focus reassuring. &lt;/p&gt;

&lt;p&gt;For now I think of Brave as an experiment. It is the first application of the block chain that I can relate to.&lt;/p&gt;

&lt;p&gt;In the future I will keep you updated once I have delved deeper into the crypto trading portion and the support of content creators.&lt;/p&gt;

</description>
      <category>crypto</category>
      <category>security</category>
    </item>
    <item>
      <title>Renting a server - This is what I learned</title>
      <dc:creator>topjer</dc:creator>
      <pubDate>Wed, 28 Apr 2021 20:24:02 +0000</pubDate>
      <link>https://forem.com/topjer/renting-a-server-this-is-what-i-learned-3dk7</link>
      <guid>https://forem.com/topjer/renting-a-server-this-is-what-i-learned-3dk7</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Maybe I should create a website?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This thought came up in my mind irregularly in the past. Usually when I thought about which creative outlet I should use. One option, the creation of videos, I did pick up but it did not feel like the right means for many things that I feel I want to say.&lt;/p&gt;

&lt;p&gt;Blogging came to mind next since it focuses on the content and less about the delivery. It is content distilled down to the essence, so to speak. Yet, whenever I gave that option some thought my perfectionism countered with many questions, like "Which platform to use for blogging?" or "Use a paid variant or a free for the beginning?" and the most important question: "Which domain should I register?".&lt;/p&gt;

&lt;p&gt;Feeling overwhelmed by this barrage of questions I usually decided to stop thinking about it.&lt;/p&gt;

&lt;p&gt;This week something was different. I stumbled over a &lt;a href="https://www.youtube.com/watch?v=gwUz3E9AW0w&amp;amp;t=539s" rel="noopener noreferrer"&gt;video of NetworkChuck&lt;/a&gt; where he was telling his viewers that they need to create their on website ... like right now. Seeing this video was the proverbial straw that broke the camels back. &lt;br&gt;In the past weeks I have become more "productive" in my free time. Finally, I started to do some programming projects that had been on my mind for quite some time. The more I programmed the more I wished to write about it. So NetworkChuck's message came at the perfect time.&lt;/p&gt;

&lt;h1&gt;
  
  
  The birth of a new site
&lt;/h1&gt;

&lt;p&gt;Are you aware how easy it is to create a new site? The term child's play comes to mind. It takes less than 5 minutes and can even be for free.&lt;/p&gt;

&lt;p&gt;To me it was immediately clear that I would choose a paid version. The prime reason being a simple one ... control. Another reason being that a paid site looks more professional. "topjer.host-your-site-for-free.com" simply does not have the same ring to it as "topjer.net" does. Lastly, the lack of ads also helps to give the site a clean look.&lt;/p&gt;

&lt;p&gt;I briefly swayed from NetworkChuck's recommendation to check out other web hosting options only to quickly return to the one he mentions, &lt;a href="https://www.hostinger.com/" rel="noopener noreferrer"&gt;hostinger.com&lt;/a&gt;.&lt;br&gt;It seemed like all hoster would basically give you the same service with minor differences. I feared that going into the details this early in my "career" would make it crash before it could even lift off.&lt;/p&gt;

&lt;p&gt;Here is now the first thing I learned in the process:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Prices for web hosting are somewhat non-transparent.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Hostinger advertised a web server for 2,39€ a month. You even get a free domain on top. Sounds too good to be true? Well, it kinda isn't. See, you get the price of 2,39€ a month only if you agree to rent the server for 48 months. And afterwards you have to pay 4,99€ a month.&lt;br&gt;If you don't want to be bound to Hostinger and want to be able to cancel the subscription on a monthly basis you have to pay 7,99€. &lt;/p&gt;

&lt;p&gt;Remember the free domain I was also talking about? Well, it is free for a year and than you have to start paying. Depending on the suffix you choose the price can vary greatly. I went with a '.net' which will be 12€ annually starting from the second year. Initially, I had my eye on '.tech' until I found out that this would cost a whopping 45€ a year.&lt;br&gt;If you break it down to a month this is still less than 4€ but since I had no idea how good blogging works for me, I went for the cheaper alternative.&lt;/p&gt;

&lt;p&gt;As a side note I want to mention that I had to pay up front. At least I did not see the option to pay monthly. So expect to pay a bigger amount initially when getting your own site.&lt;/p&gt;

&lt;p&gt;In all fairness, I want to say that Hostinger is by no means "the black sheep" in the herd of web hoster. On the contrary, it seems like most providers of web hosting services use similar tactics. &lt;br&gt;In the limited selection of hoster I checked out, Hostinger seemed to be one of the less non-transparent.&lt;/p&gt;

&lt;h1&gt;
  
  
  Did anyone say child's play?
&lt;/h1&gt;

&lt;p&gt;After I have stomached the final price and decided on the domain - a process that took surprisingly long for the fact that I went with such a simple address in the end - I was finally able to start my blog.&lt;/p&gt;

&lt;p&gt;As the foundation of my site I wanted to use WordPress which apparently is used by the majority of websites in the world wide web. &lt;a href="https://techjury.net/blog/percentage-of-wordpress-websites/#gref" rel="noopener noreferrer"&gt;We are talking about 60% of the software built sites&lt;/a&gt;.&lt;br&gt;Surely it will be easier this way than to code the site by hand. Well, I guess this is true. Especially since I know very little about front end development. Next to nothing to be honest. But it is by no means "easy".&lt;/p&gt;

&lt;p&gt;Since WordPress is powerful tool I was faced with a plethora of options right from the start. Use WordPress to customize the outline of site. Elementor can be used for parts of the content. Interested in analytics? Simply make a Google analytics account and start with OptinMonster. Forms. Plugins. Search Engine optimization.&lt;/p&gt;

&lt;p&gt;Right from the get go I made a grave mistake. I decided to create my side according to a template. This filled the side with some example content. My pride forbids me to tell you how long it took me to remove said content. Too long! Don't get me started on the endeavor of adding my socials to the side. There were many ways to present them on the site. Each working a bit differently.&lt;/p&gt;

&lt;p&gt;So here is my next lesson&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Tool assisted website creation is complex. So keep it simple in the beginning!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  A door opened
&lt;/h1&gt;

&lt;p&gt;There is one aspect which I completely neglected until now.&lt;/p&gt;

&lt;p&gt;The initial goal was to find an outlet, to give me a medium to communicate my thoughts. To document my journey. Yet, I have attained so much more.&lt;/p&gt;

&lt;p&gt;Having a whole web server for yourself creates possibilities that every developer can appreciate. The plan I choose did not only give me a hosted WordPress. No! It gave me a whole web server to play around with.&lt;/p&gt;

&lt;p&gt;I can simply ssh onto the server and snoop around. Create cronjobs. Run my own scripts on it 24/7. Create databases and connect my scripts to it. &lt;br&gt;Do you remember me talking about professionalism in the beginning? What is more professional than having a mail address like contact@topjer.net?&lt;/p&gt;

&lt;p&gt;Of course, compared to dedicated solutions the server I now have is lacking and probably laughable. But do not forget: I got all of that on top! I did not know that I would need all of that. Now that I have it, I am excited to play around with it. I can still switch to a more powerful dedicated server should I outgrow my current solution. After all it has gotten nearly as easy to get your own virtual machine in the cloud nowadays.&lt;/p&gt;

&lt;p&gt;With renting this server I have embarked on a journey. The surprise about the prices and the almost overwhelming number of options aside the start of this journey has been mostly positive. Most importantly it has made me excited for what is about to come...&lt;/p&gt;

</description>
      <category>website</category>
      <category>wordpress</category>
    </item>
  </channel>
</rss>
