<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Alex Kaszynski</title>
    <description>The latest articles on DEV Community by Alex Kaszynski (@akaszynski).</description>
    <link>https://dev.to/akaszynski</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%2F464049%2F98e8669c-344c-4b4d-8ee4-009227f19083.jpeg</url>
      <title>DEV Community: Alex Kaszynski</title>
      <link>https://dev.to/akaszynski</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/akaszynski"/>
    <language>en</language>
    <item>
      <title>Create an Azure Self-Hosted Agent with Python without going Insane</title>
      <dc:creator>Alex Kaszynski</dc:creator>
      <pubDate>Sat, 27 Feb 2021 22:30:57 +0000</pubDate>
      <link>https://dev.to/akaszynski/create-an-azure-self-hosted-agent-without-going-insane-173g</link>
      <guid>https://dev.to/akaszynski/create-an-azure-self-hosted-agent-without-going-insane-173g</guid>
      <description>&lt;p&gt;I regularly use Azure for my day-job or other software work, and depending on the task at hand, it's necessary to setup self-hosted agents to get your work done.  For those of you uninitiated, Azure Pipelines are a way of automating CI/CD and, once you've gotten over the initial learning curve, are genuinely great.  At the time of writing this, open-source software projects could have up to 10 simultaneous "agents", hosted for free on Azure's cloud.  However, for private projects you're a bit more limited.  You get a single agent for a few thousand hours and this is generally insufficient for all but most basic projects.  This is where self-hosted agents come in.  You setup your own environment and plug it into Azure's job scheduler to get sorta the best of both worlds.&lt;/p&gt;

&lt;p&gt;Being Saturday and COVID, I decided to work on making a private pipeline more efficient by adding a self-hosted agent to the existing pipeline.  Since I'm cheap, I'm only willing on shelling out the $60 it takes to have two simultaneous, unlimited MS agents and waiting for the CI tasks to complete was really getting on my nerves.  Since I have a home server/NAS that's mostly sitting there, I might as well make use of its processor.  Should be easy right?&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting Up the Agent
&lt;/h3&gt;

&lt;p&gt;Let me prerequisite this by first saying that I'm no fan of Microsoft Windows.  The cumulative time developers have spent in the past decade dealing with the OS idiosyncrasies, inefficiencies, and bad business practices enabled by a past corporate culture is a crime against humanity.  That being said, the MS of today has genuinely pivoted to the cloud and open-source and rather than killing GitHub after it's acquisition, I would argue that it's improved under their leadership.  Not something I'd expect from Microsoft.  While I genuinely think that Windows, or at least the kernel, will be replaced with Linux one day as Windows slides into obsolescence, but think that Microsoft has a bright future ahead in the areas of cloud and software development.  And honestly, there's far more money there than just in the OS anyway.&lt;/p&gt;

&lt;p&gt;Windows has great documentation for setting up the agent and installing it locally at &lt;a href="https://docs.microsoft.com/en-us/azure/devops/pipelines/agents/v2-linux"&gt;Self-hosted Linux agents&lt;/a&gt;, and there's very little I can add to the setup process.  Note that for the agent to be started automatically to enable the service with &lt;code&gt;./svc.sh install&lt;/code&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Pipeline Configuration
&lt;/h4&gt;

&lt;p&gt;With the agent installed, the next step is to modify your &lt;code&gt;azure-pipelines.yml&lt;/code&gt;.  For example, I added my agent, &lt;code&gt;ds9&lt;/code&gt;, to the pool &lt;code&gt;ds9_pool&lt;/code&gt; and added that into the yml with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- job: Linux
  pool: ds9_pool
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From the old configuration of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  pool:
    vmImage: 'ubuntu-latest'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, create a branch with these changes and submit push it.  Provide you already have the pipeline setup, this "Linux" job will automatically run on your new agent.  I say "run" but I really mean, "fail almost instantly" because if you're migrating from a Microsoft-hosted agent, you'll need to do some extra work getting it compatible.&lt;/p&gt;

&lt;h4&gt;
  
  
  Pipeline Migration to Self-Hosted Agent
&lt;/h4&gt;

&lt;p&gt;First, a little background on my pipeline.  My pipeline compiles a python module that has several C/C++ extensions that are built using &lt;code&gt;Cython&lt;/code&gt;.  Overall, it's a great setup, I have the choice to write in &lt;code&gt;Cython&lt;/code&gt; or to write pure C/C++ and then interface with it from Cython.  For my project this saves a lot of time writing C extension interface code in pure C, which is often brittle and difficult to code.&lt;/p&gt;

&lt;p&gt;However, using C extensions means that I can't just compile the package as a pure-python and ship it.  I've got to build it on the environment and platform matching where it will be deployed.  In this case, that's Windows and Linux for Python 3.6 - Python 3.8.  In the past I would have to manually build it for each release, and all the build scripts and unit testing would take a minimum of a week per release (regardless of patch or major).  Creating a pipeline is absolutely critical to getting the real work done of making better software.&lt;/p&gt;

&lt;p&gt;The beginning of my "Linux" job looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;jobs:
- job: Linux
  strategy:
    matrix:
      Python36:
        python.version: '3.6'
        python.architecture: 'x64'
      Python37:
        python.version: '3.7'
        python.architecture: 'x64'
      Python38:
        python.version: '3.8'
        python.architecture: 'x64'
  pool: ds9_pool
  steps:
    - template: .ci/checkout.yml
    - task: UsePythonVersion@0
      inputs:
        versionSpec: $(python.version)
      displayName: 'Use Python $(python.version)'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is where the first issue migrating will crop up.  The &lt;code&gt;UsePythonVersion@0&lt;/code&gt; task requires Python to be installed on the self-hosted machine.  For example, assuming you're using the default &lt;code&gt;_work&lt;/code&gt; agent directory, your directory structure should look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$AGENT_TOOLSDIRECTORY/
    Python/
        {version number}/
            {platform}/
                {tool files}
            {platform}.complete
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Microsoft provides documentation for setting this up at &lt;a href="https://docs.microsoft.com/en-us/azure/devops/pipelines/tasks/tool/use-python-version?view=azure-devops#how-can-i-configure-a-self-hosted-agent-to-use-this-task"&gt;Use Python Version task&lt;/a&gt;, but it's not complete.  For example, what's in &lt;code&gt;{tool files}&lt;/code&gt;?  Can I use &lt;code&gt;3.8&lt;/code&gt; in place of &lt;code&gt;3.8.5&lt;/code&gt; in &lt;code&gt;{version number}&lt;/code&gt;.  Is there an automated way of installing Python?&lt;/p&gt;

&lt;p&gt;Turns out it takes a bit of work to figure out, but it's actually quite straightforward once you know a few tricks:&lt;/p&gt;

&lt;h4&gt;
  
  
  Installing and Setting up Python
&lt;/h4&gt;

&lt;p&gt;The directory structure of Python 3.8 should look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$AGENT_TOOLSDIRECTORY/
    Python/
        3.8/
            x64/
                bin/
                include/
                lib/
                share/
            x64.complete
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You could manually download, install, and copy over Python to this directory, but it's far better to simply use a Python virtual environment as this is repeatable and far more reliable.  First, ensure you have &lt;code&gt;python3.8-venv&lt;/code&gt; installed with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo apt install python3.8-venv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, cd into your &lt;code&gt;_tool&lt;/code&gt; directory and create a Python directory with &lt;code&gt;mkdir Python&lt;/code&gt;.  I need three versions of Python, 3.6, 3.7, and 3.8.  Since the machine already has 3.8 installed from Ubuntu 20.04, it's easy to create a virtual environment since the base version is already installed.  However, first you'll need to create a &lt;code&gt;3.8&lt;/code&gt; directory as this is how Azure expects the directories to be setup.  However, since you might wish to reference the exact version of python, it's best to create a directory of the full version of Python and then symlink it to the directory without the patch version.  For example, the version installed after running &lt;code&gt;python3 -V&lt;/code&gt; is &lt;code&gt;Python 3.8.5&lt;/code&gt;, so you would create a symlink with &lt;code&gt;ln -s 3.8.5 3.8&lt;/code&gt;.  Your directory structure should now look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$AGENT_TOOLSDIRECTORY/
    Python/
        3.8/ -&amp;gt; 3.8.5/
        3.8.5/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;cd into &lt;code&gt;3.8.5&lt;/code&gt; and create your virtual enviornment with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python3.8 -m venv x64
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's &lt;code&gt;x64&lt;/code&gt; because that's how Azure expects it to be setup (i.e. &lt;code&gt;{version}/{architecture}&lt;/code&gt;).  Finally, &lt;code&gt;touch x64.complete&lt;/code&gt; to add the &lt;code&gt;x64.complete&lt;/code&gt; file within the &lt;code&gt;3.8.5&lt;/code&gt; directory.  I'm not sure why this is necessary given that all the information is there in the directory structure, but alas, some poor programmer probably had a reason to do this.&lt;/p&gt;

&lt;p&gt;The directory structure should now look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$AGENT_TOOLSDIRECTORY/
    Python/
        3.8 --&amp;gt; 3.8.5
        3.8.5/
            x64/
                bin/
                include/
                lib/
                share/
            x64.complete
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This has the advantage of using the system Python as a symlink, not as a copy, yet retaining separate directories for all Azure installed packages.  For all intensive purposes, it's a virtual environment setup just for the Azure agent.&lt;/p&gt;

&lt;h4&gt;
  
  
  The Other Python Versions
&lt;/h4&gt;

&lt;p&gt;Ubuntu 20.04 comes with Python3.8, but for the older versions of Python, you'll need to add the deadsnakes repository and install them from there.  Do this with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo add-apt-repository ppa:deadsnakes/ppa
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since my pipeline requires the Python headers, I need to install the &lt;code&gt;-dev&lt;/code&gt; version, and in order to create the virtual environment, you'll need to install &lt;code&gt;-venv&lt;/code&gt;, so for the other two versions of Python, the full installation command is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo apt install python3.7-dev python3.7-venv python3.6-dev python3.6-venv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, starting in the &lt;code&gt;Python&lt;/code&gt; directory, create the virtual environments for 3.6 and 3.7 with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PY36_VER=$(python3.6 -c "import sys; print('.'.join([f'{val}' for val in sys.version_info[:3]]))")
mkdir $PY36_VER
ln -s $PY36_VER 3.6
cd $PY36_VER
python3.6 -m venv x64
touch x64.complete
cd ..

PY37_VER=$(python3.7 -c "import sys; print('.'.join([f'{val}' for val in sys.version_info[:3]]))")
mkdir $PY37_VER
ln -s $PY37_VER 3.7
cd $PY37_VER
python3.7 -m venv x64
touch x64.complete
cd ..
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The directory structure should look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$AGENT_TOOLSDIRECTORY/
    Python/
        3.6 --&amp;gt; 3.6.13
        3.6.13/
            x64/
                bin/
                ...
            x64.complete
        3.7 --&amp;gt; 3.7.10
        3.7.10/
            x64/
                bin/
                ...
            x64.complete
        3.8 --&amp;gt; 3.8.5
        3.8.5/
            x64/
                bin/
                ...
            x64.complete
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that if your pipeline does not include a step to update pip, you may have to add it in.  The version included with Python 3.6 was incompatible with my workflow and had to be manually upgraded.&lt;/p&gt;

&lt;h4&gt;
  
  
  Installing other Packages and Requirements
&lt;/h4&gt;

&lt;p&gt;Azure hosted images contain several popular software packages, and if you're migrating to a fresh environment, it's necessary to install quite a few of them manually.  In my case, I needed the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo apt install docker zip libglu1-mesa
sudo snap install docker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Zip was for the &lt;code&gt;ArchiveFiles@2&lt;/code&gt; step, &lt;code&gt;libglu1-mesa&lt;/code&gt; was for vtk, and &lt;code&gt;docker&lt;/code&gt; was for the building the Python wheels on &lt;code&gt;quay.io/pypa/manylinux2010_x86_64&lt;/code&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Performance Improvements and Next Steps
&lt;/h4&gt;

&lt;p&gt;Using Microsoft hosted agents is a great way to run CI/CD on the cloud without owning hardware; it scales well and is well suited to building software.  However, in my case, I had extra hardware and spare time, so it was worth the time investment to add an additional agent to the pipeline.  My builds run about 3x faster using the additional agent, and I could probably find another (perhaps Windows) computer to run the build.&lt;/p&gt;

&lt;p&gt;However, I wasn't just going after performance improvements.  There's some data that I'd rather not to be on the "cloud", and it's important to run these functional tests pre-release.  I've been neglecting them because they're not automated, and there's usually an edge case that crops up because I failed to run them.  The next step is to add in these functional tests, save the benchmarks (using &lt;code&gt;pytest-benchmark&lt;/code&gt;), and add it in as a pre-release step.&lt;/p&gt;

</description>
      <category>python</category>
      <category>devops</category>
      <category>azure</category>
    </item>
    <item>
      <title>What I learned from Creating a Million Dollar Amazon Business</title>
      <dc:creator>Alex Kaszynski</dc:creator>
      <pubDate>Sat, 05 Sep 2020 21:43:04 +0000</pubDate>
      <link>https://dev.to/akaszynski/what-i-learned-from-a-million-dollar-amazon-business-3njb</link>
      <guid>https://dev.to/akaszynski/what-i-learned-from-a-million-dollar-amazon-business-3njb</guid>
      <description>&lt;h3&gt;
  
  
  or: How I Learned to Stop Worrying about the Books and Love Python
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4gPfMyCH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/3f7p0dhdrps0tphx4h18.JPG" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4gPfMyCH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/3f7p0dhdrps0tphx4h18.JPG" alt="Alt Text" width="800" height="536"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Textbooks...&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I have what was a million dollar grossing Amazon business under &lt;a href="https://www.amazon.com/sp?_encoding=UTF8&amp;amp;asin=&amp;amp;isAmazonFulfilled=&amp;amp;isCBA=&amp;amp;marketplaceID=ATVPDKIKX0DER&amp;amp;orderID=&amp;amp;protocol=current&amp;amp;seller=A2TY5N2S51NHI6&amp;amp;sshmPath="&gt;Brooke's Books&lt;/a&gt;, named after my lovely wife Brooke.  It was started in 2009 while I was a senior at the Air Force Academy after my brother suggested I sell a few of my older textbooks on Amazon.  Ironically, at the time I had scoffed at the idea and asked him to sell them for me; fortunately he refused and I quickly learned how easy it was to sell on Amazon.  What began as a small venture selling textbooks for other students for a minimal profit, soon transformed to an all out effort to buy and sell books on Amazon.&lt;/p&gt;

&lt;p&gt;What follows is my business and programming journey from a near worthless startup with Excel to seasoned expert using Python and the modern tools of the trade.&lt;/p&gt;
&lt;h3&gt;
  
  
  Business Genesis : The horrors of Excel VBA
&lt;/h3&gt;

&lt;p&gt;When I left the Academy and moved to my first Air Force assignment in Ohio, I no longer had access to a huge college textbook market and almost gave up on the business as I started my studies at AFIT (Air Force Institute of Technology).  I dabbled in library and yard sales, but there were almost no current textbooks that would sell for anything less than a few dollars online.  After some time, I discovered that eBay was a far superior marketplace to buy from as some auctions would either be missing key data (like the ISBN) or were being sold in the bottom of the market.  College textbook prices tend peak in January and August when students go back to school, and if you sell in December and May through July, you're selling when everyone else is while there's no demand.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--oHd2SCoT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/0vr33t0iyfm63b8qy2ax.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--oHd2SCoT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/0vr33t0iyfm63b8qy2ax.png" alt="Alt Text" width="800" height="602"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Standard eBay Textbook Posting&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;My education at the Academy was focused in Astronautical engineering, and at the time programming wasn't even remotely emphasized.  That meant automating the searching, parsing, and bidding of the eBay auctions using what I knew best: Microsoft Excel.  I still had a license left over from school and used that to perform web scraping of eBay for any auction containing a textbook that ending in the next 24 hours using Excel VBA.  This worked out surprisingly quite well, and served as the foundation for the business over the next year and a half.&lt;/p&gt;

&lt;p&gt;Over this time, I was starting to learn about the cyclic behavior of the market, the aforementioned January and August peaks versus the December and May lows by tracking price history.  In a time where free price history resources like &lt;a href="https://camelcamelcamel.com/"&gt;CamelCamelCamel&lt;/a&gt; or &lt;a href="https://keepa.com/#!"&gt;Keepa&lt;/a&gt; didn't exist, this meant maintaining my price database entirely in, again, Excel.  When buying books on eBay, I needed to know the maximum price they would sell at peak so I knew how to place my maximum bids.  The early price collection system would use an external program to collect a single data point for each day for one of the hundred thousand books I was tracking at the time, and save it as a csv to be read into excel.  While this process was slow and manual, it was the best I had as I was ignorant of Python and there was no other way of getting the history of a product on Amazon.&lt;/p&gt;

&lt;p&gt;In 2011 the business had plateaued at the time to around $50k of profit a year, which was reasonable, but I was always looking for ways to improve it.  I still recall the moment where I looked at an excel plot for the past history of a law textbook and realized that instead of performing market arbitrage (buying from eBay and selling on Amazon), I could focus on &lt;em&gt;market timing&lt;/em&gt;.  Here's an example of a price history plot you can generate right now using the keepa API:&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="nn"&gt;keepa&lt;/span&gt;
&lt;span class="n"&gt;api&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;keepa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Keepa&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'&amp;lt;your API key from https://keepa.com/#!api&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# query and plot
&lt;/span&gt;&lt;span class="n"&gt;asin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'1628101326'&lt;/span&gt;
&lt;span class="n"&gt;products&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;asin&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;keepa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;plot_product&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;products&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;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2i0x9ceL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/pp28cd73cs9nsggbzjkj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2i0x9ceL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/pp28cd73cs9nsggbzjkj.png" alt="Alt Text" width="800" height="557"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Cyclic Price Behavior of a Textbook&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This textbook has clear periodic peaks at the start of the fall semester, and when I saw a similar plot of this back in 2011, I realized that I could focus entirely on buying &lt;em&gt;and&lt;/em&gt; selling just on Amazon.  No more sifting through eBay listings!  Now that there are resources that track Amazon prices, you don't have to maintain your own database, but for me, still fresh out of college and with almost no business experience, I was floored by this discovery.  With this asymmetric information, I knew when to buy, when to sell, and how much I could afford to buy textbooks for given the estimated future price of a textbook.&lt;/p&gt;
&lt;h3&gt;
  
  
  Business Expansion : Transition and Suffering with MATLAB
&lt;/h3&gt;

&lt;p&gt;The business profitability doubled the next year to $100k and my excel database was getting strained.  Even using an excel binary file, the spreadsheet containing my database was over a gigabyte and just opening it up would take around 5 minutes.  Querying for a textbook would take another 10 seconds, and it was becoming difficult to maintain a tracking database.&lt;/p&gt;

&lt;p&gt;At the time, I was an Air Force officer working at the Air Force Research Laboratory at Wright Patterson AFB, and the Air Force used (and sadly still uses) a lot of MATLAB.  I was starting to get into coding and automation there and decided to transition my excel codebase and database over to MATLAB.  At the time, this seemed like a logical choice given MATLAB's strong suite of database functionality, and I was fine with shelling out the cost of a license to increase the business profitability.  Since I'd never heard of Python, and MATLAB was leaps and bounds ahead of Excel VBA, I was fine with transition.  I even created a GUI within MATLAB that allowed me to have a spreadsheet-like experience with a list of books I was tracking on the bottom half of the screen and a plot and entry box on the top of the screen to plot the price history.&lt;/p&gt;

&lt;p&gt;Over the next two years from 2012 to 2014, I ran the business solely with the help of MATLAB and my grit.  Due to MATLAB's proprietary nature and lack of great third party packages, whenever I wanted to add functionality to the program or database, I had to write it myself.  At the time, there was no integration with Amazon's API; when creating shipments or tracking inventory I had to use excel or web forms.  Grabbing data still required external programs to pull data from Amazon and loading CSV files was manually done.  In other words, like Excel, it worked, but it wasn't optimal and worse I was stuck entirely within MATLAB's ecosystem of pay-to-use toolboxes.&lt;/p&gt;
&lt;h3&gt;
  
  
  Business Maturity : Translation and Adaptation to Python
&lt;/h3&gt;

&lt;p&gt;Three things happened around the same time to force me to transition out of MATLAB to a superior language.  First, I was leaving the Air Force and would be starting to work as a subcontractor for them while working remotely.  At the time I had to purchase another license of MATLAB, and really didn't want to have two licenses (one for the book business and one as an Air Force subcontractor).  I was getting frustrated with the inflexibility of MATLAB's licensing and toolboxes, and had even considered moving to Octave before discovering Python.  I can't remember exactly how I had discovered it, but I recall installing the Anaconda Python distribution and immediately feeling comfortable with Spyder given how similar it was with MATLAB's IDE.  Learning that there was more to programming than matrices and arrays really took me by surprise coming from a MATLAB only background.  When I realized that I could install someone else's module with &lt;code&gt;pip install &amp;lt;module&amp;gt;&lt;/code&gt;, I was sold.&lt;/p&gt;

&lt;p&gt;The second reason to transition to a more flexible, modular programming ecosystem was my shift from fulfillment by merchant (FBM) to fulfillment by seller (FBA).  For years I had been personally buying, sorting, binning, repackaging and shipping thousands of books a year.  At one point, the post office even refused to deliver or pickup my books because it was straining their resources!  Back in 2013, I was personally contacted by Amazon to transition my inventory to fulfillment by Amazon, which was at the time looking for early adopters.  I needed a new method of inventory management as I needed to be able to interface with Amazon's API.  Enter the world of Python modules!  The &lt;a href="https://pypi.org/project/boto/"&gt;boto&lt;/a&gt; module worked out of the box and allowed me to automate my shipments, inventory management, and even pricing of my entire inventory.  After a month, I had a collection of  personal inventory and pricing scripts all using a free software and community maintained, open-source modules.  I could even start writing my own classes:&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="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MWS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;object&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="s"&gt;"""Interface with MWS"""&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="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="s"&gt;""" initializes mws api """&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;connected&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Open&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="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="s"&gt;""" Opens connection to Amazon MWS """&lt;/span&gt;

        &lt;span class="c1"&gt;# Establish global MWS connection
&lt;/span&gt;        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Establishing MWS connection...'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MWSConnection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ACCESSKEYID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SECRETKEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;validate_certs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SellerId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MERCHANT&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Merchant&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MERCHANT&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MarketplaceId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MARKETPLACEID&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Established'&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;get_single_order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;orderID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;callback&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="s"&gt;""" Returns text on a single order """&lt;/span&gt;
        &lt;span class="c1"&gt;# check for internet connection before submitting
&lt;/span&gt;        &lt;span class="n"&gt;connected&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;allowException&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="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Requesting %s order information'&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;orderID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AmazonOrderId&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;orderID&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

        &lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetOrderResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Order&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;orders&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;This enabled me to finally solve my third goal: automated price research and database maintenance.  The &lt;a href="https://pypi.org/project/bottlenose/"&gt;bottlenose&lt;/a&gt; module allows access to Amazon's product advertising API, which can be used for price research.  It's fairly limited in the number of requests that you an make with it, but if you limit the number of products you track, you can update an entire database (I had pared mine down to 50k books), overnight, all within Python, as a scheduled python script.&lt;/p&gt;

&lt;p&gt;These changes were necessary as I was moving on from being an Air Force officer to becoming a subcontractor with a, what some may find ironic, higher workload.  I had also hired a subcontractor of my own to deal with the repackaging and shipment of books from individual sellers to Amazon's warehouses, and I needed precise knowledge over inventory so that I could track the any book from purchase to eventual sale, which wasn't possible within MATLAB without writing everything from scratch.  With Python, it generally meant looking for anything between a code snippet on &lt;a href="https://stackoverflow.com/"&gt;Stack Overflow&lt;/a&gt; or a full blown module on GitHub or PyPi, saving weeks if not months of effort.  Want to read excel files?  Try &lt;a href="https://github.com/python-excel/xlrd"&gt;xlrd&lt;/a&gt; or &lt;code&gt;openpyxl&lt;/code&gt;.  Need to automate the tracking of shipments to check if they were delivered?  Use &lt;a href="https://pypi.org/project/tracking-url/"&gt;tracking-url&lt;/a&gt;.  Large databases?  &lt;code&gt;numpy&lt;/code&gt; or &lt;code&gt;pandas&lt;/code&gt;.  Web scraping?  &lt;code&gt;beautifulsoup4&lt;/code&gt;.  Validate ISBNs?  &lt;code&gt;pyisbn&lt;/code&gt;.  Schedule scripts or programs from python?  &lt;code&gt;python-crontab&lt;/code&gt;.  The list goes on and on and on.&lt;/p&gt;

&lt;p&gt;With the magic of &lt;code&gt;pyqt&lt;/code&gt;, I created a fully featured GUI that allowed me to track which books could be purchased, query Amazon for the listing, preview the history of the book, and check my own inventory in real-time all without even opening up a browser.  As the database was loaded in advance, I didn't need to manually query for anything and with the use of a cache, I could instantly have all the data I needed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Sk31o-JF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/0ufz85adx64fs64qv2c8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Sk31o-JF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/0ufz85adx64fs64qv2c8.png" alt="Alt Text" width="800" height="592"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Brooke's Books PyQt GUI&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Over the next few years, I kept incrementally improving my code to make it easier to maintain and track changes.  I converted the collection of scripts to a genuine python module so I could install it on multiple machines, incorporated GitHub for private code backup, and even added some (very) basic unit tests with &lt;code&gt;pytest&lt;/code&gt; to make sure that I didn't break anything between my revisions.&lt;/p&gt;

&lt;p&gt;At the peak of the business in 2016, I was bringing in $250k per year and I was only investing about 4 to 8 hours a week maintaining the code and running the software.  For a short time, I thought that the business would go on indefinitely.&lt;/p&gt;
&lt;h3&gt;
  
  
  Competition : Upping my Python Game with the Cloud
&lt;/h3&gt;

&lt;p&gt;Like all good things, especially those with low cost of entry, other people were quick to figure out and enter the business.  For some time, I was able to be competitive without changing the business model or my purchasing algorithm, but I was soon finding that other sellers were undercutting me by a few cents during peak sales season and winning the buy box.  It got so bad that during a two week selling period (Christmas break no less) I had to spend several hours each day manually updating prices to ensure that I remained competitive.  Given the release schedule of college textbooks, missing out on a semester meant that your book is no longer the most current edition and effectively worthless.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--CCVoioYu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/rt3tugi6cxluvi2op82x.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CCVoioYu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/rt3tugi6cxluvi2op82x.jpg" alt="Alt Text" width="800" height="440"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Skiing at Chamonix, France&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;While on a ski trip in 2018 with my wife and (at the time) three kids and unable to manually micromanage book prices, I realized that I needed to create a script/module that could automatically, and persistently update my prices for me within certain parameters.  Given the inconsistency of the hotel WiFi, this wasn't something I trusted to run on my local computer, so I resorted to creating a script that would run on the cloud (Google cloud to be specific) on one of their free offerings.  That evening, after managing the kids and going on a few runs, I wrote a very basic script that would:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Grab my current inventory using &lt;code&gt;boto&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Check the competitive prices through  &lt;code&gt;boto&lt;/code&gt; or &lt;code&gt;bottlenose&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Modify my prices based on a certain price threshold&lt;/li&gt;
&lt;li&gt;Submit the prices and repeat.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And behold, it actually worked:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;brookesbooks.inventory       - DEBUG    - Loading inventory file /home/alex/.brookesbooks/databases/inventory/1599180665.tsv
brookesbooks.inventory       - DEBUG    - Requesting inventory
brookesbooks.mws             - DEBUG    - Requesting _GET_FBA_MYI_UNSUPPRESSED_INVENTORY_DATA_ report
brookesbooks.mws             - DEBUG    - Report attempt 1...
brookesbooks.mws             - DEBUG    - Getting report 79083018510
brookesbooks.mws             - DEBUG    - Acquired report 79083018510
brookesbooks.inventory       - DEBUG    - Inventory saved to /home/alex/.brookesbooks/databases/inventory/1599334709.tsv
brookesbooks.inventory       - DEBUG    - Loading inventory file /home/alex/.brookesbooks/databases/inventory/1599334709.tsv
brookesbooks.manager         - INFO     - Starting persistent undercut
brookesbooks.inventory       - DEBUG    - Loading inventory file /home/alex/.brookesbooks/databases/inventory/1599180665.tsv
brookesbooks.mws             - DEBUG    - Getting competitive pricing
brookesbooks.mws             - DEBUG    - Competitive pricing    0 of 13
brookesbooks.mws             - DEBUG    - Competitive pricing   10 of 13
brookesbooks.mws             - INFO     - Acquired Competitive pricing
brookesbooks.manager         - DEBUG    - Changing 1454868295_VG from $26.19 to $26.05
brookesbooks.manager         - DEBUG    -   Due to compeditive price $26.14
brookesbooks.manager         - DEBUG    -   Original price was       $26.19
brookesbooks.manager         - DEBUG    - Changing 1454868406_LN from $97.00 to $96.71
brookesbooks.manager         - DEBUG    -   Due to compeditive price $96.75
brookesbooks.manager         - DEBUG    -   Original price was       $97.00
brookesbooks.manager         - DEBUG    - Changing 1454881798_A from $146.98 to $146.04
brookesbooks.manager         - DEBUG    -   Due to compeditive price $146.10
brookesbooks.manager         - DEBUG    -   Original price was       $146.98
brookesbooks.manager         - DEBUG    - Changing 1454881798_N from $220.89 to $220.70
brookesbooks.manager         - DEBUG    -   Due to compeditive price $220.79
brookesbooks.manager         - DEBUG    -   Original price was       $220.89
brookesbooks.manager         - DEBUG    - Changing 163043051X_G from $34.08 to $33.82
brookesbooks.manager         - DEBUG    -   Due to compeditive price $33.85
brookesbooks.manager         - DEBUG    -   Original price was       $34.08
brookesbooks.manager         - DEBUG    - Changing 1640208453_A from $124.89 to $124.68
brookesbooks.manager         - DEBUG    -   Due to compeditive price $124.78
brookesbooks.manager         - DEBUG    -   Original price was       $124.89
brookesbooks.mws             - DEBUG    - Sending 6 item price feed
brookesbooks.mws             - DEBUG    - 1454868295_VG 26.05
brookesbooks.mws             - DEBUG    - 1454868406_LN 96.71
brookesbooks.mws             - DEBUG    - 1454881798_A  146.04
brookesbooks.mws             - DEBUG    - 1454881798_N  220.70
brookesbooks.mws             - DEBUG    - 163043051X_G  33.82
brookesbooks.mws             - DEBUG    - 1640208453_A  124.68
brookesbooks.mws             - DEBUG    - Waiting on feed result
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In fact, it worked so well the hardest part was thinking about how to deploy my module on a remote VM and have it run consistently.  I ended up simply using my private GitHub repository, cloning it on the VM, and then running &lt;code&gt;undercut&lt;/code&gt;.  Since &lt;code&gt;brookesbooks&lt;/code&gt; was now a python module, I could install it anywhere (even on the cloud).  Here's what the &lt;code&gt;setup.py&lt;/code&gt; looks like:&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="s"&gt;"""Setup.py for Brooke's Books"""&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;setuptools&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;setup&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;io&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;io_open&lt;/span&gt;

&lt;span class="n"&gt;package_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'brookesbooks'&lt;/span&gt;

&lt;span class="n"&gt;__version__&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
&lt;span class="n"&gt;version_file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&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;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&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;dirname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__file__&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;package_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'_version.py'&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;io_open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;version_file&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="s"&gt;'r'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

&lt;span class="n"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;package_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;package_name&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;

    &lt;span class="c1"&gt;# Version
&lt;/span&gt;    &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;__version__&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Brooke's Books"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="c1"&gt;# Author details
&lt;/span&gt;    &lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'Alex Kaszynski'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;author_email&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'akascap@gmail.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="n"&gt;scripts&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'brookesbooks/brookesbooks'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
             &lt;span class="s"&gt;'brookesbooks/undercut'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;

    &lt;span class="c1"&gt;# Run-time dependencies
&lt;/span&gt;    &lt;span class="n"&gt;install_requires&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'numpy'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'bottlenose'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'boto'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'matplotlib'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'pandas'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                      &lt;span class="s"&gt;'pyqt5'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'xlrd'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'pyisbn'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'python-crontab'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                      &lt;span class="s"&gt;'tqdm'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'pyqtgraph'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'beautifulsoup4'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'openpyxl'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                      &lt;span class="s"&gt;'keepa'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'lxml'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'tracking_url'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'boto'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'qdarkstyle'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Over the next few days, I could actually log in to the VM on my iPhone using either &lt;code&gt;terminus&lt;/code&gt; with SSH keys I'd generated, or Google's app.  I was partial to &lt;code&gt;terminus&lt;/code&gt; as I really liked their interface, so it was worth it to create SSH keys and upload those.  With this, I could check query my inventory status, competitive pricing, and status of my remote application all on my phone over a terminal without investing time creating a web application!  My biggest challenge was not dropping the phone on the chairlift.&lt;/p&gt;

&lt;h3&gt;
  
  
  Moving on
&lt;/h3&gt;

&lt;p&gt;With this and a several other tweaks, I had bought some time to consider my options on Amazon.  At this time, I was living in Germany and still employed with AFRL as a sub-contractor.  The business was doing well, but was nowhere as profitable in the past due to the number of competitors that had come to Amazon to do (what I'm guessing) the exact same thing I was doing.  While I was investing remarkably little time in the business, I still had to do all the accounting, taxes (in two countries...), and customer service that goes along with an online business.  Profit margins per book, which had started out as an astronomical $35 per book, had fallen to $5 to $10 with many textbooks barely breaking even.  FBA storage pricing had gone up, and I was paying my second subcontractor over $15,000 a year in inventory management.&lt;/p&gt;

&lt;p&gt;The business was still profitable, but my time investment with AFRL, my family, and (gasp!) recreation meant that time had become more valuable to me and I had less motivation to dedicate to the business.  In the middle of 2019 when I started to work for ANSYS, I made the decision to begin to exit the business at the end of the year.  I purchased my last book in December and have been selling off the rest of my inventory since.  As of the writing of this, I have 137 books left; down from my peak of 7000.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ python3 -c "from brookesbooks import inventory; print(inventory.Inventory().total)"
137
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While I'm still selling books, I'm not too worried about potential competition at this point as I'm nearly out, which is why I'm fine not redacting the ASINs and ISBNs of some of the books that I've used.  As with anything, it's more about the algorithm than just the idea, and I should be able to sell off the last bit this winter.  I think there's still life in this business, but it's no longer at the level of profitability to make it worthwhile to invest my time in.  I'm ready to move on to something new.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lessons Learned
&lt;/h3&gt;

&lt;p&gt;One of the most important things I learned over the decade of the business was, without a doubt, the need for delegation.  In the beginning, I liked to code everything myself within Excel VBA and MATLAB and rarely used other people's work.  With the advent of open-source solutions, Stack Overflow, and emerging languages like Python, it's become much easier to use other people's solutions.  I wish I had focused using what others had created earlier on.  When looking at a project or goal, think first of how you can utilize someone else's work and then only as a last resort write your own.  I see too many cases where organizations, small and large, choose to write their own private code when they could use something maintained by the community because of organizational inflexibility or mistrust of open-source.  I think the software community is coming around, but I still see a remarkable amount of wheel-reinventing in both the public, private, and government sectors.&lt;/p&gt;

&lt;p&gt;This same principle of delegation also applies outside of coding.  I took too long to hire someone to deal with physical inventory management and should have taken steps to interview, hire, and delegate the responsibility of handling the textbooks to someone I trusted.  To those that I did finally hire, I thank you for your over 6 years of loyal and steadfast work taking care of what I couldn't.&lt;/p&gt;

&lt;h4&gt;
  
  
  Smaller Lessons
&lt;/h4&gt;

&lt;p&gt;There are a variety of general software lessons learned that most will see as common knowledge, but I still think it's important to say:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Feel free to write simple scripts, but anything complex should be modularized.  And don't extend your python path.  &lt;code&gt;pip install -e .&lt;/code&gt; it.&lt;/li&gt;
&lt;li&gt;Premature optimization is the root of all evil.  That week that you optimized a function could have been a week introducing a new feature that saved you a month of work.&lt;/li&gt;
&lt;li&gt;Slow code is better than no code.  Get something that works first and then optimize it once you have nothing else better to do.&lt;/li&gt;
&lt;li&gt;Document your code if you think you'll use it more than once.  That's basically 99.9% of your code.&lt;/li&gt;
&lt;li&gt;Please use git and GitHub.  This seems obvious, but I think new coders are scared of git.  Upload your code to GitHub under a private repository and keep it there as a backup.  You won't regret it and might even thank yourself if your laptop gets stolen, or worse, you run &lt;code&gt;rm -rf *&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Get comfortable with the command line regardless of your OS.  You'll hate it at first, learn to like it, and eventually wonder why you'd use anything else.&lt;/li&gt;
&lt;li&gt;Don't write the GUI first or concurrent with the core backend.  Write the core module first and &lt;em&gt;maybe&lt;/em&gt; put a GUI if you think you need a UI.  Writing the GUI first is asking for pain.  Get something that works as a standalone service/program and then think about the UI.  UI development is over half the battle, and you might find you don't even need it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There's more, but it's been a long post already.&lt;/p&gt;

&lt;h3&gt;
  
  
  Final Thoughts
&lt;/h3&gt;

&lt;p&gt;If you're new or have no experience with programming and need to automate something, be sure to check out Python.  I feel that universities are doing their students a disservice by not exposing them to Python and just giving them experience with MATLAB, Excel, or various other proprietary products (I'm looking at you Mathematica).  Python can just about do it all.  For free.  And if it can't, someone will probably add it, and that person might just be you.&lt;/p&gt;

&lt;h4&gt;
  
  
  Contributing to Open Source
&lt;/h4&gt;

&lt;p&gt;Like parenting or teaching, what you gain by giving of yourself is usually far and beyond your investment of time.  Case in point, I was hired because of my open source module &lt;a href="https://github.com/akaszynski/pyansys"&gt;pyansys&lt;/a&gt;, and I've had numerous offers for part-time work (which I've had to turn down) because of &lt;a href="https://github.com/akaszynski/keepa"&gt;keepa&lt;/a&gt;.  Additionally, I have a friend who was hired in part of his work with &lt;a href="https://github.com/pyvista/pyvista"&gt;pyvista&lt;/a&gt;, which used to be &lt;code&gt;vtki&lt;/code&gt;, another module I had created because I saw there was a missing feature that could benefit the community in general.&lt;/p&gt;

&lt;p&gt;Open source is the future, and as Microsoft, Google, and Amazon have shown, it doesn't mean the end of profitable software.  In fact, the opposite is more true now more than ever.  Most closed source software I've seen contains about 90% duplication, 9% new, and less than 1% genuine critical proprietary code.  Having someone else maintain that 90% to 99% is a boon, not a bane.  It's that 1% that makes all the difference, so focus on that and leave the rest for someone else to maintain.  And given what I've seen, they'll do a better job at it than you ever would have, &lt;em&gt;for free&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Module
&lt;/h3&gt;

&lt;p&gt;I might at some point clean up and post my &lt;code&gt;brookesbooks&lt;/code&gt; module should there be any requests for it.  If you're interested, please let me know.  It's not a trivial amount of work to clean it up, so I won't do it unless there's a genuine need for it.&lt;/p&gt;

&lt;p&gt;Thanks for reading!&lt;/p&gt;

</description>
      <category>python</category>
      <category>amazon</category>
    </item>
  </channel>
</rss>
