This is a short post to tell you to create more bash aliases.
I have had one for my devops setup for a while because I have to set several environment variables to get just about anything done. More recently, though, I set up a few more for my main Django project and even this blog. For the blog it's a simple as entering the directory and activating the environment:
alias pmlstart='cd ~/pml_blog && source env/bin/activate'
For the Django project, it took me forever to remember how to start my local postgres instance (is it service start, or start service, or...?), so I finally just made that part of an alias for when I start working on the project:
alias webstart="cd ~/website && source env/bin/activate && sudo service postgresql start && source env_vars.sh"
Since writing this, I added that last piece to it to load environment variables into my environment so I don't have to remember to edit my activate file when I need to recreate my environment locally. Highly recommended!
Are there bash commands you use frequently together? Make an alias!
There are a few code formatting tools I like to use in just about any Python (and Django) project: black, isort, and flake8. These all serve slightly different purposes, and there are alternatives to each. A full discussion/comparison of code formatting tools in Python is beyond the scope of this post*, but in brief:
Black is great because it automatically formats my code with a well recognized style. I have my editor (currently VSCode) set to run black when I save a file. This makes my code consistent, and is great in teams I've worked in because it means we don't have to worry about styling in code reviews.
isort is great for sorting imports. Even though I tend to do a bit of this sorting as I go, I inevitably swap an import or leave one out of place. This organizes them so I don't have to.
flake8 catches minor (and sometimes not so minor) code issues like unused variables.
There are a few ways to configure these libraries, but for now, I have them in their own individual config files. These should all be placed at the root of your Django project.
For black, I use the default config. In some other projects, I've extended the line length, but currently I'm leaving black as default.
For isort, isort.cfg is the filename and mine looks like this:
Where env is the name of your environment. The extended skip for migrations isn't necessary, but I wanted to avoid changing my migration files. The black profile is nice to avoid conflicts between black and isort. Skipping your environment is likely required. When I've left this out (or accidentally run isort without the config) isort runs against the libaries in my environment and ends up breaking them by creating circular dependencies. One way to make sure you aren't going to do this is to run isort --check-only . to make sure it's only running against your files before actually running it.
For flake8 .flake8 is the filename and mine looks like this:
These exclude all of the files in the folders migrations, __pycache__ and env (where env is the name of your environment). You may want to exclude more, or not exclude some of these, but I've found this to work for me.
Finally, on other projects I've used all of these with pre-commit, which is a library that uses git hooks to prevent you from committing code that doesn't use the standards you and your team have set, such as black, isort, and flake8, or some other combination of code formatting tools. It also allows you to use a single command to run all three tools.
Unfortunately, I can't figure out how to use pre-commit in my current setup with my python projects in WSL and my git client (Fork) in Windows. Perhaps this is a sign I should start using the git CLI more. But if you know of a good way to use precommit with this setup, let me know!
*between writing and publishing this I read James Bennett covering similar ground. He goes a bit deeper on some of the tools I mention (and other related tools), so I wanted to link this for further reading.
In the time since I've posted on this blog, I've erased the environment I used to set up and run Jekyll. Since I've never been a Ruby programmer and most of the code I write now is in Python, I thought I'd look into a blogging engine in Python. I found Pelican and Nikola to be recommended by a number of folks, so I decided to give Pelican a try, and it's been great so far! Using a Python based site generator feels more comfortable for me since I'm more familiar with pip than gems.
It seems remarkably similar to Jekyll and I only had to make a few small changes to the front matter on a few posts to get all of my content working with Pelican. I've made some tweaks to the default theme to move a few icons around to my liking, but otherwise haven't made major changes. I'll make a follow up post if I make interesting changes that others might want to use.
While I was trying to incorporate Stripe’s Javascript library into my codebase, I ran into some issues implementing it in Typescript. I eventually was able to fix those issues, but while I was trying to figure that out, I left the Stripe code as Javascript and stumbled upon an interesting way to interface that with my Typescript code compiled by WebPack.
First, at the bottom of the Typescript file you want to call from Javascript:
I found the documentation for presigned URLS on AWS using boto to be insufficient, so here is how I formed the request.
I wanted to have a file private in S3, but expose it to a user for download if they were authorized to access it. I found the easiest way to do that was leave the object in S3 as private, but use the AWS API to generate a pre-signed URL with a low timeout (a minute or so). Unfortunately, I found the documentation not so straightforward and cobbled together a few Stack Overflow answers to come up with exactly what I needed. In particular, I needed to add the ‘ResponseContentType’ to get the file to download correctly, and needed to specify my AWS credentials.
I’m using Django to serve up the download via a get request:
classGetDownloadURL(APIView):defget(self,request):# Get the service client.session=boto3.session.Session(profile_name="AWSUserName")s3=session.client("s3")# Generate the URL to get 'key-name' from 'bucket-name'url=s3.generate_presigned_url(ClientMethod="get_object",Params={"Bucket":"your-s3-bucket","Key":"SampleDLZip.zip","ResponseContentType":"application/zip",},ExpiresIn=100,)returnResponse(url)
Notes: You will need to update the Key and Bucket params to match what you have in S3.
Depending how you have set up your AWS credentials, you may be able to omit the ‘profile_name="AWSUserName"’ parameter. I prefer to be explicit in my config because I’ve run into issues when I have used the default in the past.
I originally created this blog using an EC2 instance on AWS and used that to run Jekyll and host the site. Recently, I shut down that instance in order to move the site generation to my local machine(s) and host the site on S3. This article was very helpful to me when setting up HTTPS with S3
For now, my Jekyll setup is pretty basic, and I’ve only made a handful of changes:
-I created a pages folder to hold the few non-blog-post pages (currently just About, Books, and Projects) to clean up the home directory. I accidentally moved the homepage into the pages folder at one point and was very annoyed and confused at my homepage not updating when I’d rebuild with changes. Consider yourself warned!
-Instead of building using the command line, I created a simple bash script to build the site then send the changes to S3:
#!/bin/bash#set destination and source so that we can run from anywhere#helpful when editing posts in the _posts directoryJEKYLL_ENV=productionbundleexecjekyllbuild-d/mnt/c/blog_location/_site-s/mnt/c/blog_location
#remove this script from the site directory.
rm/mnt/c/blog_location/build-blog.sh
#upload to s3
awss3sync--delete--size-only/mnt/c/blog_location//_site/s3://programmingmylife.com/--profileMyProfile
The file paths are /mnt/c/ because I typically build the site from my local Windows Subsystem for Linux install.
-Added google analytics. I tried following a blog post to do this, but it broke the page (I must have mistyped something on the includes), but it made me realize jekyll now has GA built in! You just need to add the line:
‘google-analytics: UA-11111111-1 ‘
to your _config.yml (where UA-11111111-1 is your GA tracking ID) and set JEKYLL_ENV=production before building.
-I got rid of categories in the URLs, which is the default. This was as easy as adding ‘permalink: /:year/:month/:day/:title.html’ to _config.yml
-I also wanted to add the full contents of posts to my home page. I couldn’t find a config setting to change this, so I had to edit the minima theme. First, I had to find the theme files with
bundleshowminima
Then, I edited _layouts/home.html to include all post content as shown here. I may change this as the number of posts increases.
I’d like to improve the styling on the blog or get a different theme at some point, but for now, it is mostly the current (at the time of posting) default for Jekyll.
A website I am currently creating has a very simple front end, but I wanted to be able to swap out instances of my API URLs in TypeScript depending which environment I am working in or building for. So far they are all ‘http://127.0.0.1:8000/’, but when I deploy, I didn’t want to have to remember to set a URL for prod or staging etc.
Unfortunately, I was unable to find a good way to do this with just the TypeScript compiler for static files. I’m not a big fan of the complexity that webpack adds, but I’ve noticed it also minifies and obfuscates my javascript, which is a nice bonus. If you know of a good way to do this with the TypeScript compiler alone, please let me know!
I tried a few different methods, but a combination of using webpack and splitting my config into development and production and DefinePlugin worked. Otherwise, I mostly just followed the installation instructions for webpack including installing locally. I tried installing globally based on other tutorials, but I ran into a bunch of issues doing that.
Also, I’m a little frustrated with how confusing the documentation was on this. I tried using Environment Variables in Webpack, but I couldn’t figure out how to access those in my code (not just the config file). It also took me some searching to understand how to use DefinePlugin because the documentation does not make it clear where that should go or how to include it. I found thesetwo links that helped me figure it out (see my webpack.dev.js, webpack.staging.js, and webpack.prod.js files below).
With this setup, I run ‘npm run build:test’, ‘npm run build:staging’, or ‘npm run build:prod’ depending if I am working locally or building for production. Those commands are mapped in package.json:
{"name":"av_frontend","version":"1.0.0","description":"","private":true,"scripts":{"test":"echo \"Error: no test specified\" && exit 1","build:test":"webpack --config webpack.dev.js --watch ","build:staging":"webpack --config webpack.staging.js","build:prod":"webpack --config webpack.prod.js"},"author":"","license":"ISC","dependencies":{"@types/bootstrap":"^4.1.2","@types/node":"^10.12.12","bootstrap":"^4.1.3"},"devDependencies":{"ts-loader":"^5.3.1","typescript":"^3.2.2","webpack":"^4.27.1","webpack-cli":"^3.1.2","webpack-merge":"^4.1.4"}}
I added --watch on test so that when I’m developing in VSCode, I can just leave it running and it’ll update whenever I save a file. If I want to run that manually I have to run ‘node_modules/.bin/webpack --config webpack.dev.js --watch’ because I installed webpack locally, not globally.
When I first got this set up, I realized it was only outputting a single Javascript file because I didn’t understand how ‘entry’ and ‘output’ worked in the webpack config file (see code below). Now I define each entry JS file for each page (they both have an import for my config with the IP addresses) with a name. Under ‘output’ [name].js corresponds with each ‘entry’. So my output ends up in the dist/ folder and the two files are named ‘login.js’ and ‘purchase.js’ based on the two fields in the ‘entry’ object. Any files added to 'entry' will produce a corresponding output Javascript file.
I also missed something in the instructions for Typescript when I was initially setting this up that lead to a long hunt for why it wasn’t including my config.ts file (the error messages were not great). Don’t forget the ‘resolve’ field below or webpack will get confused when trying to import any file with a .ts extension referenced in another file.
I wrote this blog post originally thinking I had solved this problem, but the solution I was using can only handle development and production environments. It also is a little more complicated than the above solution. It is described here: https://basarat.gitbooks.io/typescript/content/docs/tips/build-toggles.html
I am creating a Django application using PostgreSQL (PSQL) for my database and was nearly finished with the API when I discovered some strange behavior. After successfully testing the API in the Django app, I decided to run some basic queries on the database. I received the following error for nearly every field in the app:
I was getting the same result for every field in the table that I tried (and when I try to include the table name as the hint suggests), except for ‘user_id’ and ‘objective'.
I confirmed that the fields existed using \d+ game_progress_gameplaykeys, tried changing some of their field types, and even upgraded from Postgres 9.5 to 10.5 (I was planning to do this anyway).
I created camelCase field names in my Django app based on what the field names were previously in my application (written in C#).
I decided to fix this (for now) by fixing my models to all use snake_case and using https://github.com/vbabiy/djangorestframework-camel-case to switch the keys from camelCase to snake_case when they come into the API. One issue solved!
While debugging that issue, I decided to update my laptop’s code + postgres version since I hadn’t worked on it in a while and wanted to see if the issue was just on my desktop. When I reinstalled PSQL, I couldn’t seem to log into it using the user I was creating. Using the postgres user was fine, though.
I finally figured out the issue was that PSQL was running on port 5433, not 5432 (the default). After that, I was puzzling over what could be running on 5432 since ‘netstat’ and ‘lsof’ revealed nothing else running on my WSL Ubuntu VM. As I was searching around, I saw someone mention that really only PSQL should be running on that port, and I realized I had installed PSQL on Windows on that machine before I moved over to WSL. I uninstalled that, switched back to 5432 in Linux, restarted PSQL, and boom, good to go.
While I was debugging that issue, I learned some good information about PSQL along the way:
/etc/postgresql/10/main/postgresql.conf allows you to set and check the port that PSQL is running on.
/etc/postgresql/10/main/pg_hba.conf allows you to set different security protocols for connections to PSQL. Notable for local development: set the local connection lines to ‘trust’ so you don’t have to enter a password when logging in.
Note: you need to restart the PSQL server for either of these changes to take effect.
Note 2: MORE IMPORTANT NOTE: Don’t use trust anywhere other than a local version of PSQL. Ever.
These are the lines I had to change to get that to work (may be different in versions of PSQL other than 10.5):
I had a little trouble finding a simple way to set NGINX up to work locally, so I wanted to write up some quick instructions here. I’m using NGINX with Windows Subsystem for Linux (WSL).
First, I installed NGINX in WSL with ‘sudo apt-get install nginx’
Then, I created a symlink to my frontend directory in my home directory in WSL.
In /etc/nginx/conf.d, I created basic config file localhost.conf:
server {
listen 8080;
location / {
root /home/username/frontend_directory;
index index.html index.htm;
}
# redirect server error pages to the static page /50x.html
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
The only thing you should need to change is the bolded frontend_directory. Keep in mind the path may differ depending where you keep your files. I restructured my front end after setting up Webpack to include a dist/ folder and broke this config before modifying this line again.
To start NGINX (I have to do this every time I restart the computer): ‘sudo nginx’
Then go to 127.0.0.1:8080 and your site should be live!
If you need to make changes to your configuration, you should first try: ‘sudo service nginx reload’ which will give you a ‘hot’ reload instead of restarting the server.
If you do need to restart the server, you can do so with ‘sudo service nginx restart’.
For a new project, I wanted to use TypeScript on the front end but not any of the frameworks that usually include it (React, Angular, etc.). Unfortunately, this means that when I have been trying to figure out how to do something in TypeScript, searches often lead me to solutions involving those frameworks.
I still haven’t found a good resource for creating a JSON object and sending it to a backend using TypeScript. The easiest solution would be to relax the TypeScript compiler and writing it the same way we would in JavaScript, but that defeats the point of using TypeScript. In looking at example code, I found that creating an interface to describe the JSON object is one accepted way to do it.
interfaceIJSON{email:string;fullName:string;shortName:string;password:string;institution:string;isStudent:boolean;}consturl='http://127.0.0.1:8000/register/';functiongatherData(e:Event){e.preventDefault();//don't reload page so that we can test.letjson:IJSON={email:(<HTMLInputElement>document.getElementById("email")).value,fullName:(<HTMLInputElement>document.getElementById("fullName")).value,shortName:(<HTMLInputElement>document.getElementById("shortName")).value,password:(<HTMLInputElement>document.getElementById("password")).value,institution:(<HTMLInputElement>document.getElementById("institution")).value,isStudent:true,}sendDataViaFetch(json);}functionsendDataViaFetch(json:IJSON){varrequest=newRequest(url,{method:'POST',body:JSON.stringify(json),headers:newHeaders({'Content-Type':'application/json','Authorization':this.basic})});fetch(request).then(function(){// Handle response we get from the API});}window.addEventListener('submit',gatherData);