<?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: chris48s</title>
    <description>The latest articles on DEV Community by chris48s (@chris48s).</description>
    <link>https://dev.to/chris48s</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%2F624938%2F0bd425c3-3ba3-4cb1-a2ba-7010ef1944d5.png</url>
      <title>DEV Community: chris48s</title>
      <link>https://dev.to/chris48s</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/chris48s"/>
    <language>en</language>
    <item>
      <title>Identifying Abandoned PyPI Packages</title>
      <dc:creator>chris48s</dc:creator>
      <pubDate>Fri, 23 Aug 2024 20:10:45 +0000</pubDate>
      <link>https://dev.to/chris48s/identify-abandoned-pypi-packages-with-pip-abandoned-1el4</link>
      <guid>https://dev.to/chris48s/identify-abandoned-pypi-packages-with-pip-abandoned-1el4</guid>
      <description>&lt;p&gt;Relying on abandoned and deprecated packages in our applications is generally something we want to avoid. &lt;a href="https://github.com/chris48s/pip-abandoned" rel="noopener noreferrer"&gt;pip-abandoned&lt;/a&gt; can help with this. In some packaging ecosystems, the registry allows you to mark a package as deprecated or abandoned. For example in NPM:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbnl2xouqv4g91t35oej3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbnl2xouqv4g91t35oej3.png" alt="NPM Deprecated Package" width="800" height="326"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;and Packagist:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flhm4niw17ripvdnat07x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flhm4niw17ripvdnat07x.png" alt="Packagist Abandoned Package" width="800" height="185"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This also allows package managers to consume this metadata to provide a warning at install time:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftr11rxrmo6gttzgez1ng.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftr11rxrmo6gttzgez1ng.png" alt="NPM Install Warnings" width="735" height="198"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;PyPI doesn't have this concept. The registry does not provide any way to abandon or deprecate a package, and this makes it harder to tell if you are relying on a package which is no longer maintained. However, there are some signals we can look at. The best of which is: If a package on PyPI is linked to a GitHub repository and that GitHub repository is archived, this is a strong signal that the package itself is no longer maintained.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/chris48s/pip-abandoned" rel="noopener noreferrer"&gt;pip-abandoned&lt;/a&gt; takes into account several signals and allows us to search a virtual environment or &lt;code&gt;requirements.txt&lt;/code&gt; file to identify suspected abandoned or deprecated packages.&lt;/p&gt;

&lt;p&gt;If abandoned packages are found, pip-abandoned will produce a summary:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3q8bhf10ocac3vit7kjg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3q8bhf10ocac3vit7kjg.png" alt="Pip-abandoned Summary Report" width="736" height="493"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The tool exits with code 0 when no abandoned packages were found and a non-zero code when one or more abandoned packages were found. This means you can use it as a CI check as well as for ad-hoc audits.&lt;/p&gt;

</description>
      <category>pip</category>
      <category>python</category>
      <category>package</category>
      <category>pypi</category>
    </item>
    <item>
      <title>Failing the CI build if django migrations are out of date</title>
      <dc:creator>chris48s</dc:creator>
      <pubDate>Wed, 12 May 2021 19:13:10 +0000</pubDate>
      <link>https://dev.to/chris48s/failing-the-ci-build-if-django-migrations-are-out-of-date-16ei</link>
      <guid>https://dev.to/chris48s/failing-the-ci-build-if-django-migrations-are-out-of-date-16ei</guid>
      <description>&lt;p&gt;A common mistake in django is to make a model change but forget to run &lt;code&gt;makemigrations&lt;/code&gt; to generate a migration for the model change. Sometimes it is not entirely obvious when this need to happen. For example, let's say I'm using the &lt;a href="https://github.com/django-extensions/django-extensions"&gt;django-extensions&lt;/a&gt; library and I define a model 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="c1"&gt;# models.py
&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django_extensions.db.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TimeStampedModel&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TimeStampedModel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this situation, upgrading django-extensions to a new version might require me to regenerate the migrations in my app, even though I haven't made any changes to &lt;code&gt;models.py&lt;/code&gt; and overlooking this could generate unexpected results.&lt;/p&gt;

&lt;p&gt;Fortunately there is a simple thing I can do to detect and warn if this happens: If I run&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python manage.py makemigrations --check
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;in my CI build, this will cause the build to fail if my migrations are out of sync with the models, warning me about the problem.&lt;/p&gt;

</description>
      <category>django</category>
      <category>python</category>
    </item>
    <item>
      <title>Three useful pathlib snippets</title>
      <dc:creator>chris48s</dc:creator>
      <pubDate>Thu, 06 May 2021 16:19:19 +0000</pubDate>
      <link>https://dev.to/chris48s/three-useful-pathlib-snippets-3c9l</link>
      <guid>https://dev.to/chris48s/three-useful-pathlib-snippets-3c9l</guid>
      <description>&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="nn"&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="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'/foo/bar'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="c1"&gt;# Read in a text file
&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="s"&gt;'file.txt'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;read_text&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Recursively list all .csv files in directory
&lt;/span&gt;&lt;span class="n"&gt;csvs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rglob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'*.csv'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="c1"&gt;# Iterate files or subdirectories
&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;iterdir&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_file&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;
&lt;span class="n"&gt;subdirs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;iterdir&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_dir&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>python</category>
      <category>pathlib</category>
    </item>
    <item>
      <title>Pathlib Cheat Sheet</title>
      <dc:creator>chris48s</dc:creator>
      <pubDate>Thu, 06 May 2021 16:18:16 +0000</pubDate>
      <link>https://dev.to/chris48s/pathlib-cheat-sheet-2mp0</link>
      <guid>https://dev.to/chris48s/pathlib-cheat-sheet-2mp0</guid>
      <description>&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;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="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&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;home&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="s"&gt;'foo.tar.gz'&lt;/span&gt;
&lt;span class="c1"&gt;# same as f = Path('/home/chris/foo.tar.gz')
&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_file&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="bp"&gt;True&lt;/span&gt;

&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_dir&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="bp"&gt;False&lt;/span&gt;

&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="bp"&gt;True&lt;/span&gt;

&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;PosixPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'/home/chris/foo.tar.gz'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;as_uri&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="s"&gt;'file:///home/chris/foo.tar.gz'&lt;/span&gt;

&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;as_posix&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="s"&gt;'/home/chris/foo.tar.gz'&lt;/span&gt;

&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parts&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'/'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'home'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'chris'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'foo.tar.gz'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;suffix&lt;/span&gt;
&lt;span class="s"&gt;'.gz'&lt;/span&gt;

&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;suffixes&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'.tar'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'.gz'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt;
&lt;span class="n"&gt;PosixPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'/home/chris'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>python</category>
      <category>pathlib</category>
    </item>
    <item>
      <title>Editing Commit Timestamps in Git</title>
      <dc:creator>chris48s</dc:creator>
      <pubDate>Mon, 03 May 2021 18:44:39 +0000</pubDate>
      <link>https://dev.to/chris48s/editing-commit-timestamps-in-git-3a8f</link>
      <guid>https://dev.to/chris48s/editing-commit-timestamps-in-git-3a8f</guid>
      <description>&lt;p&gt;I always forget that &lt;a href="https://help.github.com/en/github/committing-changes-to-your-project/why-are-my-commits-in-the-wrong-order"&gt;GitHub shows commits in a pull request in timestamp order&lt;/a&gt; rather than tree/history order until just after I've just pushed a monster rebase and everything's in the wrong order. To force GitHub to show the commits in the correct order, we need to edit the commit timestamps to match the history order. To do this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git rebase -i HEAD~N
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mark the commits that need re-ordering as &lt;code&gt;edit&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;edit f3b9e40 Reticulate Splines
edit 68f39b8 Adjust Bell Curves
edit d605e5a Dice Models
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At each stage of the rebase:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git commit --amend --date=now --no-edit
git rebase --continue
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the timestamp ordering will match the history. When you force-push, GitHub will order the commits correctly in your pull request.&lt;/p&gt;

</description>
      <category>git</category>
      <category>github</category>
      <category>terminal</category>
    </item>
  </channel>
</rss>
