How To Set Up a Private Docker Registry on Top of DigitalOcean Spaces and Use It with DigitalOcean Kubernetes

The author selected the Free and Open Source Fund to receive a donation as part of the Write for DOnations program.

Introduction

A Docker registry is a storage and content delivery system for named Docker images, which are the industry standard for containerized applications. A private Docker registry allows you to securely share your images within your team or organization with more flexibility and control when compared to public ones. By hosting your private Docker registry directly in your Kubernetes cluster, you can achieve higher speeds, lower latency, and better availability, all while having control over the registry.

The underlying registry storage is delegated to external drivers. The default storage system is the local filesystem, but you can swap this for a cloud-based storage driver. DigitalOcean Spaces is an S3-compatible object storage designed for developer teams and businesses that want a scalable, simple, and affordable way to store and serve vast amounts of data, and is very suitable for storing Docker images. It has a built-in CDN network, which can greatly reduce latency when frequently accessing images.

In this tutorial, you’ll deploy your private Docker registry to your DigitalOcean Kubernetes cluster using Helm, backed up by DigitalOcean Spaces for storing data. You’ll create API keys for your designated Space, install the Docker registry to your cluster with custom configuration, configure Kubernetes to properly authenticate with it, and test it by running a sample deployment on the cluster. At the end of this tutorial, you’ll have a secure, private Docker registry installed on your DigitalOcean Kubernetes cluster.

Prerequisites

Before you begin this tutorial, you’ll need:

  • Docker installed on the machine that you’ll access your cluster from. For Ubuntu 18.04 visit How To Install and Use Docker on Ubuntu 18.04. You only need to complete the first step. Otherwise visit Docker’s website for other distributions.

  • A DigitalOcean Kubernetes cluster with your connection configuration configured as the kubectl default. Instructions on how to configure kubectl are shown under the Connect to your Cluster step shown when you create your cluster. To learn how to create a Kubernetes cluster on DigitalOcean, see Kubernetes Quickstart.

  • A DigitalOcean Space with API keys (access and secret). To learn how to create a DigitalOcean Space and API keys, see How To Create a DigitalOcean Space and API Key.

  • The Helm package manager installed on your local machine, and Tiller installed on your cluster. Complete steps 1 and 2 of the How To Install Software on Kubernetes Clusters with the Helm Package Manager. You only need to complete the first two steps.

  • The Nginx Ingress Controller and Cert-Manager installed on the cluster. For a guide on how to do this, see How to Set Up an Nginx Ingress with Cert-Manager on DigitalOcean Kubernetes.

  • A domain name with two DNS A records pointed to the DigitalOcean Load Balancer used by the Ingress. If you are using DigitalOcean to manage your domain’s DNS records, consult How to Manage DNS Records to create A records. In this tutorial, we’ll refer to the A records as registry.example.com and k8s-test.example.com.

Step 1 — Configuring and Installing the Docker Registry

In this step, you will create a configuration file for the registry deployment and install the Docker registry to your cluster with the given config using the Helm package manager.

During the course of this tutorial, you will use a configuration file called chart_values.yaml to override some of the default settings for the Docker registry Helm chart. Helm calls its packages, charts; these are sets of files that outline a related selection of Kubernetes resources. You’ll edit the settings to specify DigitalOcean Spaces as the underlying storage system and enable HTTPS access by wiring up Let’s Encrypt TLS certificates.

As part of the prerequisite, you would have created the echo1 and echo2 services and an echo_ingress ingress for testing purposes; you will not need these in this tutorial, so you can now delete them.

Start off by deleting the ingress by running the following command:

  • kubectl delete -f echo_ingress.yaml

Then, delete the two test services:

  • kubectl delete -f echo1.yaml && kubectl delete -f echo2.yaml

The kubectl delete command accepts the file to delete when passed the -f parameter.

Create a folder that will serve as your workspace:

  • mkdir ~/k8s-registry

Navigate to it by running:

  • cd ~/k8s-registry

Now, using your text editor, create your chart_values.yaml file:

  • nano chart_values.yaml

Add the following lines, ensuring you replace the highlighted lines with your details:

chart_values.yaml
ingress:   enabled: true   hosts:     - registry.example.com   annotations:     kubernetes.io/ingress.class: nginx     certmanager.k8s.io/cluster-issuer: letsencrypt-prod     nginx.ingress.kubernetes.io/proxy-body-size: "30720m"   tls:     - secretName: letsencrypt-prod       hosts:         - registry.example.com  storage: s3  secrets:   htpasswd: ""   s3:     accessKey: "your_space_access_key"     secretKey: "your_space_secret_key"  s3:   region: your_space_region   regionEndpoint: your_space_region.digitaloceanspaces.com   secure: true   bucket: your_space_name 

The first block, ingress, configures the Kubernetes Ingress that will be created as a part of the Helm chart deployment. The Ingress object makes outside HTTP/HTTPS routes point to internal services in the cluster, thus allowing communication from the outside. The overridden values are:

  • enabled: set to true to enable the Ingress.
  • hosts: a list of hosts from which the Ingress will accept traffic.
  • annotations: a list of metadata that provides further direction to other parts of Kubernetes on how to treat the Ingress. You set the Ingress Controller to nginx, the Let’s Encrypt cluster issuer to the production variant (letsencrypt-prod), and tell the nginx controller to accept files with a max size of 30 GB, which is a sensible limit for even the largest Docker images.
  • tls: this subcategory configures Let’s Encrypt HTTPS. You populate the hosts list that defines from which secure hosts this Ingress will accept HTTPS traffic with our example domain name.

Then, you set the file system storage to s3 — the other available option would be filesystem. Here s3 indicates using a remote storage system compatible with the industry-standard Amazon S3 API, which DigitalOcean Spaces fulfills.

In the next block, secrets, you configure keys for accessing your DigitalOcean Space under the s3 subcategory. Finally, in the s3 block, you configure the parameters specifying your Space.

Save and close your file.

Now, if you haven’t already done so, set up your A records to point to the Load Balancer you created as part of the Nginx Ingress Controller installation in the prerequisite tutorial. To see how to set your DNS on DigitalOcean, see How to Manage DNS Records.

Next, ensure your Space isn’t empty. The Docker registry won’t run at all if you don’t have any files in your Space. To get around this, upload a file. Navigate to the Spaces tab, find your Space, click the Upload File button, and upload any file you’d like. You could upload the configuration file you just created.

Empty file uploaded to empty Space

Before installing anything via Helm, you need to refresh its cache. This will update the latest information about your chart repository. To do this run the following command:

  • helm repo update

Now, you’ll deploy the Docker registry chart with this custom configuration via Helm by running:

  • helm install stable/docker-registry -f chart_values.yaml --name docker-registry

You’ll see the following output:

Output
NAME: docker-registry ... NAMESPACE: default STATUS: DEPLOYED RESOURCES: ==> v1/ConfigMap NAME DATA AGE docker-registry-config 1 1s ==> v1/Pod(related) NAME READY STATUS RESTARTS AGE docker-registry-54df68fd64-l26fb 0/1 ContainerCreating 0 1s ==> v1/Secret NAME TYPE DATA AGE docker-registry-secret Opaque 3 1s ==> v1/Service NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE docker-registry ClusterIP 10.245.131.143 <none> 5000/TCP 1s ==> v1beta1/Deployment NAME READY UP-TO-DATE AVAILABLE AGE docker-registry 0/1 1 0 1s ==> v1beta1/Ingress NAME HOSTS ADDRESS PORTS AGE docker-registry registry.example.com 80, 443 1s NOTES: 1. Get the application URL by running these commands: https://registry.example.com/

Helm lists all the resources it created as a result of the Docker registry chart deployment. The registry is now accessible from the domain name you specified earlier.

You’ve configured and deployed a Docker registry on your Kubernetes cluster. Next, you will test the availability of the newly deployed Docker registry.

Step 2 — Testing Pushing and Pulling

In this step, you’ll test your newly deployed Docker registry by pushing and pulling images to and from it. Currently, the registry is empty. To have something to push, you need to have an image available on the machine you’re working from. Let’s use the mysql Docker image.

Start off by pulling mysql from the Docker Hub:

  • sudo docker pull mysql

Your output will look like this:

Output
Using default tag: latest latest: Pulling from library/mysql 27833a3ba0a5: Pull complete ... e906385f419d: Pull complete Digest: sha256:a7cf659a764732a27963429a87eccc8457e6d4af0ee9d5140a3b56e74986eed7 Status: Downloaded newer image for mysql:latest

You now have the image available locally. To inform Docker where to push it, you’ll need to tag it with the host name, like so:

  • sudo docker tag mysql registry.example.com/mysql

Then, push the image to the new registry:

  • sudo docker push registry.example.com/mysql

This command will run successfully and indicate that your new registry is properly configured and accepting traffic — including pushing new images. If you see an error, double check your steps against steps 1 and 2.

To test pulling from the registry cleanly, first delete the local mysql images with the following command:

  • sudo docker rmi registry.example.com/mysql && sudo docker rmi mysql

Then, pull it from the registry:

  • sudo docker pull registry.example.com/mysql

This command will take a few seconds to complete. If it runs successfully, that means your registry is working correctly. If it shows an error, double check what you have entered against the previous commands.

You can list Docker images available locally by running the following command:

  • sudo docker images

You’ll see output listing the images available on your local machine, along with their ID and date of creation.

Your Docker registry is configured. You’ve pushed an image to it and verified you can pull it down. Now let’s add authentication so only certain people can access the code.

Step 3 — Adding Account Authentication and Configuring Kubernetes Access

In this step, you’ll set up username and password authentication for the registry using the htpasswd utility.

The htpasswd utility comes from the Apache webserver, which you can use for creating files that store usernames and passwords for basic authentication of HTTP users. The format of htpasswd files is username:hashed_password (one per line), which is portable enough to allow other programs to use it as well.

To make htpasswd available on the system, you’ll need to install it by running:

  • sudo apt install apache2-utils -y

Note:
If you’re running this tutorial from a Mac, you’ll need to use the following command to make htpasswd available on your machine:

  • docker run --rm -v $ {PWD}:/app -it httpd htpasswd -b -c /app/htpasswd_file sammy password

Create it by executing the following command:

  • touch htpasswd_file

Add a username and password combination to htpasswd_file:

  • htpasswd -B htpasswd_file username

Docker requires the password to be hashed using the bcrypt algorithm, which is why we pass the -B parameter. The bcrypt algorithm is a password hashing function based on Blowfish block cipher, with a work factor parameter, which specifies how expensive the hash function will be.

Remember to replace username with your desired username. When run, htpasswd will ask you for the accompanying password and add the combination to htpasswd_file. You can repeat this command for as many users as you wish to add.

Now, show the contents of htpasswd_file by running the following command:

  • cat htpasswd_file

Select and copy the contents shown.

To add authentication to your Docker registry, you’ll need to edit chart_values.yaml and add the contents of htpasswd_file in the htpasswd variable.

Open chart_values.yaml for editing:

  • nano chart_values.yaml

Find the line that looks like this:

chart_values.yaml
  htpasswd: "" 

Edit it to match the following, replacing htpasswd\_file\_contents with the contents you copied from the htpasswd_file:

chart_values.yaml
  htpasswd: |-     htpasswd_file_contents 

Be careful with the indentation, each line of the file contents must have four spaces before it.

Once you’ve added your contents, save and close the file.

To propagate the changes to your cluster, run the following command:

  • helm upgrade docker-registry stable/docker-registry -f chart_values.yaml

The output will be similar to that shown when you first deployed your Docker registry:

Output
Release "docker-registry" has been upgraded. Happy Helming! LAST DEPLOYED: ... NAMESPACE: default STATUS: DEPLOYED RESOURCES: ==> v1/ConfigMap NAME DATA AGE docker-registry-config 1 3m8s ==> v1/Pod(related) NAME READY STATUS RESTARTS AGE docker-registry-6c5bb7ffbf-ltnjv 1/1 Running 0 3m7s ==> v1/Secret NAME TYPE DATA AGE docker-registry-secret Opaque 4 3m8s ==> v1/Service NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE docker-registry ClusterIP 10.245.128.245 <none> 5000/TCP 3m8s ==> v1beta1/Deployment NAME READY UP-TO-DATE AVAILABLE AGE docker-registry 1/1 1 1 3m8s ==> v1beta1/Ingress NAME HOSTS ADDRESS PORTS AGE docker-registry registry.example.com 159.89.215.50 80, 443 3m8s NOTES: 1. Get the application URL by running these commands: https://registry.example.com/

This command calls Helm and instructs it to upgrade an existing release, in your case docker-registry, with its chart defined in stable/docker-registry in the chart repository, after applying the chart_values.yaml file.

Now, you’ll try pulling an image from the registry again:

  • sudo docker pull registry.example.com/mysql

The output will look like the following:

Output
Using default tag: latest Error response from daemon: Get https://registry.example.com/v2/mysql/manifests/latest: no basic auth credentials

It correctly failed because you provided no credentials. This means that your Docker registry authorizes requests correctly.

To log in to the registry, run the following command:

  • sudo docker login registry.example.com

Remember to replace registry.example.com with your domain address. It will prompt you for a username and password. If it shows an error, double check what your htpasswd_file contains. You must define the username and password combination in the htpasswd_file, which you created earlier in this step.

To test the login, you can try to pull again by running the following command:

  • sudo docker pull registry.example.com/mysql

The output will look similar to the following:

Output
Using default tag: latest latest: Pulling from mysql Digest: sha256:f2dc118ca6fa4c88cde5889808c486dfe94bccecd01ca626b002a010bb66bcbe Status: Image is up to date for registry.example.com/mysql:latest

You’ve now configured Docker and can log in securely. To configure Kubernetes to log in to your registry, run the following command:

  • sudo kubectl create secret generic regcred --from-file=.dockerconfigjson=/home/sammy/.docker/config.json --type=kubernetes.io/dockerconfigjson

You will see the following output:

Output
secret/regcred created

This command creates a secret in your cluster with the name regcred, takes the contents of the JSON file where Docker stores the credentials, and parses it as dockerconfigjson, which defines a registry credential in Kubernetes.

You’ve used htpasswd to create a login config file, configured the registry to authenticate requests, and created a Kubernetes secret containing the login credentials. Next, you will test the integration between your Kubernetes cluster and registry.

Step 4 — Testing Kubernetes Integration by Running a Sample Deployment

In this step, you’ll run a sample deployment with an image stored in the in-cluster registry to test the connection between your Kubernetes cluster and registry.

In the last step, you created a secret, called regcred, containing login credentials for your private registry. It may contain login credentials for multiple registries, in which case you’ll have to update the Secret accordingly.

You can specify which secret Kubernetes should use when pulling containers in the pod definition by specifying imagePullSecrets. This step is necessary when the Docker registry requires authentication.

You’ll now deploy a sample Hello World image from your private Docker registry to your cluster. First, in order to push it, you’ll pull it to your machine by running the following command:

  • sudo docker pull paulbouwer/hello-kubernetes:1.5

Then, tag it by running:

  • sudo docker tag paulbouwer/hello-kubernetes:1.5 registry.example.com/paulbouwer/hello-kubernetes:1.5

Finally, push it to your registry:

  • sudo docker push registry.example.com/paulbouwer/hello-kubernetes:1.5

Delete it from your machine as you no longer need it locally:

  • sudo docker rmi registry.example.com/paulbouwer/hello-kubernetes:1.5

Now, you’ll deploy the sample Hello World application. First, create a new file, hello-world.yaml, using your text editor:

  • nano hello-world.yaml

Next, you’ll define a Service and an Ingress to make the app accessible to outside of the cluster. Add the following lines, replacing the highlighted lines with your domains:

hello-world.yaml
apiVersion: extensions/v1beta1 kind: Ingress metadata:   name: hello-kubernetes-ingress   annotations:     kubernetes.io/ingress.class: nginx     nginx.ingress.kubernetes.io/rewrite-target: / spec:   rules:   - host: k8s-test.example.com     http:       paths:       - path: /         backend:           serviceName: hello-kubernetes           servicePort: 80 --- apiVersion: v1 kind: Service metadata:   name: hello-kubernetes spec:   type: NodePort   ports:   - port: 80     targetPort: 8080   selector:     app: hello-kubernetes --- apiVersion: apps/v1 kind: Deployment metadata:   name: hello-kubernetes spec:   replicas: 3   selector:     matchLabels:       app: hello-kubernetes   template:     metadata:       labels:         app: hello-kubernetes     spec:       containers:       - name: hello-kubernetes         image: registry.example.com/paulbouwer/hello-kubernetes:1.5         ports:         - containerPort: 8080       imagePullSecrets:       - name: regcred 

First, you define the Ingress for the Hello World deployment, which you will route through the Load Balancer that the Nginx Ingress Controller owns. Then, you define a service that can access the pods created in the deployment. In the actual deployment spec, you specify the image as the one located in your registry and set imagePullSecrets to regcred, which you created in the previous step.

Save and close the file. To deploy this to your cluster, run the following command:

  • kubectl apply -f hello-world.yaml

You’ll see the following output:

Output
ingress.extensions/hello-kubernetes-ingress created service/hello-kubernetes created deployment.apps/hello-kubernetes created

You can now navigate to your test domain — the second A record, k8s-test.example.com in this tutorial. You will see the Kubernetes Hello world! page.

Hello World page

The Hello World page lists some environment information, like the Linux kernel version and the internal ID of the pod the request was served from. You can also access your Space via the web interface to see the images you’ve worked with in this tutorial.

If you want to delete this Hello World deployment after testing, run the following command:

  • kubectl delete -f hello-world.yaml

You’ve created a sample Hello World deployment to test if Kubernetes is properly pulling images from your private registry.

Conclusion

You have now successfully deployed your own private Docker registry on your DigitalOcean Kubernetes cluster, using DigitalOcean Spaces as the storage layer underneath. There is no limit to how many images you can store, Spaces can extend infinitely, while at the same time providing the same security and robustness. In production, though, you should always strive to optimize your Docker images as much as possible, take a look at the How To Optimize Docker Images for Production tutorial.

DigitalOcean Community Tutorials

PyCoder’s Weekly: Issue #364 (April 16, 2019)

#364 – APRIL 16, 2019
View in Browser »

The PyCoder’s Weekly Logo


Hands-On Python 3 Concurrency With the asyncio Module

Learn how to speed up your Python 3 programs using concurrency and the asyncio module in the standard library. See step-by-step how to leverage concurrency and parallelism in your own programs, all the way to building a complete HTTP downloader example app using asyncio and aiohttp.
REAL PYTHON video

Python Used to Take Photo of Black Hole

“Scientists have used a new algorithm to take a photo of a black hole. One of the most exciting parts about it to me is that they used a lot of Python libraries to do the magic.”
MIKE DRISCOLL

Monitor Python Applications With Datadog APM and Distributed Tracing

alt

Datadog’s tracing client integrates with asynchronous libraries like asyncio, gevent, and Tornado. Trace requests across service boundaries to identify bottlenecks, and get the necessary context to debug critical errors. Dive into your Python applications today with a free 14-day trial of Datadog →
DATADOG sponsor

Django: Keeping Logic Out of Templates (And Views)

Solid advice! Keep your Django templates and views clean by moving application logic into model methods and model managers.
BLOT.IM

How to Create an Index in Django Without Downtime

In this step-by-step Python tutorial, you’ll get a solid understanding of the limitations of Django migrations by tackling a well known problem: creating an index in Django without downtime.
REAL PYTHON

Meet Python Content Creators at PyCharm’s PyCon Booth

I’ll be there, the Real Python team will be there, and so will Mike Kennedy from Talk Python, Brian Okken from Test & Code, Matt Harrison, the PyBites team, Chris Medina, and Miguel Grinberg. Be sure to stop by and say hi 🙂 Thanks JetBrains!
JETBRAINS.COM

list.sort() vs sorted(list)

Taking a closer look at Python’s built-in list sorting methods with regards to memory consumption and time efficiency. Nice writeup!
FLORIAN DAHLITZ

PyCon 2019 Close to Sell Out

Get your ticket before it’s too late 🙂
PYCON.BLOGSPOT.COM

Discussions

Just Found the Best Python Book…Cover

(Potentially not safe for work)
REDDIT

Python Jobs

Senior Python Developer (Copenhagen, Denmark)

GameAnalytics Ltd.

Senior Python Engineer (Remote)

ReCharge Payments

Python Engineer in Healthcare (Burlington, MA)

Nuance Communications

Machine Learning and Data Science Developer (Austin, TX)

Protection Engineering Consultants LLC

More Python Jobs >>>

Articles & Tutorials

Python String Formatting Tips & Best Practices

Learn the four main approaches to string formatting in Python, as well as their strengths and weaknesses. You’ll also get a simple rule of thumb for how to pick the best general purpose string formatting approach in your own programs.
REAL PYTHON video

Python App Settings Management With Dynaconf

Nice little project for managing settings in your Python apps: “The only line of code you need to manage your Python 3 configurations”
BRUNO ROCHA

Stop Reviewing Code Manually

alt

Take the hassle out of code reviews—Codacy flags errors so you can fix them quickly. Address security concerns, code duplication, code complexity and drops in coverage, directly from your workflow. Click here to get started →
CODACY sponsor

Introduction to the Python Calendar Module

Short & sweet intro to the calendar module in the Python standard library.
STACKABUSE.COM

Be Your Own Certificate Authority

How to create a simple, internal CA for your microservice architecture or integration testing—with Python.
MOSHE ZADKA

Linear Regression in Python

In this step-by-step tutorial, you’ll get started with linear regression in Python. Linear regression is one of the fundamental statistical and machine learning techniques, and Python is a popular choice for machine learning.
REAL PYTHON

LSTMs for Human Activity Recognition

An example of using TensorFlow for Human Activity Recognition (HAR) on a smartphone data set in order to classify types of movement, e.g. “walking”, “sitting”, “standing” etc.
GUILLAUME CHEVALIER

Raspberry Pi for Computer Vision and Deep Learning

You can teach your Raspberry Pi to “see” using Computer Vision, Deep Learning, and OpenCV. Let Adrian Rosebrock show you how →
PYIMAGESEARCH sponsor

How to Write a Python Web Framework (Part 4)

In this part of Jahongir’s blog post series you’ll see how to expand your web framework with custom exception handlers, support for static files, and middleware processing.
JAHONGIR RAHMONOV

Using Python to Analyze Game of Thrones

Want to learn Python? What better way to learn than by having a Game of Thrones project to complete as motivation.
ROCKY KEV • Shared by Ricky White

Projects & Code

eht-imaging: The Code Behind the Black Hole Image

Imaging, analysis, and simulation software for radio interferometry used for generating the famous black hole image. Python FTW!
GITHUB.COM/ACHAEL

Events

PyColorado 2019 CFP

September 6 to September 8 in Denver, CO
PAPERCALL.IO

PyLadies Dublin

April 18, 2019
PYLADIES.COM

BangPypers

April 20, 2019
MEETUP.COM

SciPy Japan 2019

April 23 to April 25, 2019
SCIPY.ORG


Happy Pythoning!
This was PyCoder’s Weekly Issue #364.
View in Browser »

alt


[ Subscribe to 🐍 PyCoder’s Weekly 💌 – Get the best Python news, articles, and tutorials delivered to your inbox once a week >> Click here to learn more ]

Planet Python

Stack Abuse: Introduction to the Python Calendar Module

Introduction

Python has an built-in module named Calendar that contains useful classes and functions to support a variety of calendar operations. By default, the Calendar module follows the Gregorian calendar, where Monday is the first day (0) of the week and Sunday is the last day of the week (6).

In Python, datetime and time modules also provide low-level calendar-related functionalities. In addition to these modules, the Calendar module provides essential functions related to displaying and manipulating calendars.

To print and manipulate calendars, the Calendar module has 3 important classes: Calendar, TextCalendar, and HTMLCalendar. In this article, we will see how these classes can help implement a variety of calendar related functions.

Functionalities of the Calendar Module

To use the Calendar module, we need to first import the module using:

import calendar   

Let’s take a look at the list of useful functions in this module.

Printing Calendar for a Specific Month

We can print the calendar for a specific month, by using the below function:

calendar.month(yyyy, m, w, l)   

The arguments passed to this function are the year (yyyy), month (m), date column width (w), and the number of lines per week (l), respectively. For example, let’s use this function to print the calendar of March, 2019:

print ("Calendar of March 2019 is:")   print (calendar.month(2019, 3, 2, 1))   

Output:

Calendar of March 2019 is:        March 2019 Mo Tu We Th Fr Sa Su                1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17   18 19 20 21 22 23 24   25 26 27 28 29 30 31   

Printing Calendar for a Specific Year

We can print the calendar for a whole year, using the below function:

calendar.calendar(yyyy, w, l, c, m)   

The above function returns the calendar for the entire year, for the year specified as an argument. The arguments passed to this function are the year (yyyy), date column width (w), number of lines per week (l), number of spaces between month’s column (c), number of columns (m).

For example, to print the calendar of the year 2019, use:

print(calendar.calendar(2019, 2, 2, 6, 3))   

Output:

January                   February                   March  Mo Tu We Th Fr Sa Su      Mo Tu We Th Fr Sa Su      Mo Tu We Th Fr Sa Su      1  2  3  4  5  6                   1  2  3                   1  2  3   7  8  9 10 11 12 13       4  5  6  7  8  9 10       4  5  6  7  8  9 10  14 15 16 17 18 19 20      11 12 13 14 15 16 17      11 12 13 14 15 16 17  21 22 23 24 25 26 27      18 19 20 21 22 23 24      18 19 20 21 22 23 24  28 29 30 31               25 26 27 28               25 26 27 28 29 30 31           April                      May                       June  Mo Tu We Th Fr Sa Su      Mo Tu We Th Fr Sa Su      Mo Tu We Th Fr Sa Su   1  2  3  4  5  6  7             1  2  3  4  5                      1  2   8  9 10 11 12 13 14       6  7  8  9 10 11 12       3  4  5  6  7  8  9  15 16 17 18 19 20 21      13 14 15 16 17 18 19      10 11 12 13 14 15 16  22 23 24 25 26 27 28      20 21 22 23 24 25 26      17 18 19 20 21 22 23  29 30                     27 28 29 30 31            24 25 26 27 28 29 30            July                     August                  September  Mo Tu We Th Fr Sa Su      Mo Tu We Th Fr Sa Su      Mo Tu We Th Fr Sa Su   1  2  3  4  5  6  7                1  2  3  4                         1   8  9 10 11 12 13 14       5  6  7  8  9 10 11       2  3  4  5  6  7  8  15 16 17 18 19 20 21      12 13 14 15 16 17 18       9 10 11 12 13 14 15  22 23 24 25 26 27 28      19 20 21 22 23 24 25      16 17 18 19 20 21 22  29 30 31                  26 27 28 29 30 31         23 24 25 26 27 28 29                                                      30          October                   November                  December  Mo Tu We Th Fr Sa Su      Mo Tu We Th Fr Sa Su      Mo Tu We Th Fr Sa Su      1  2  3  4  5  6                   1  2  3                         1   7  8  9 10 11 12 13       4  5  6  7  8  9 10       2  3  4  5  6  7  8  14 15 16 17 18 19 20      11 12 13 14 15 16 17       9 10 11 12 13 14 15  21 22 23 24 25 26 27      18 19 20 21 22 23 24      16 17 18 19 20 21 22  28 29 30 31               25 26 27 28 29 30         23 24 25 26 27 28 29                                                      30 31 

Note: Instead of using “print”, we can alternately use calendar.prmonth() and calendar.pryear() functions to print the month and year calendars. These functions print the output on your terminal.

Checking for a Leap Year

We can use isleap() function to check if a year is a leap year or not. The year is passed as an argument to the function and the function returns True if the year is a leap, otherwise it returns False if the year is not a leap. Let’s use this function to see if the year 2016 is leap:

calendar.isleap(2016)   

Output:

True   

Number of Leap Years within Range

It is also possible to check the number of leap years in a given range of years, specified as an argument to the below function:

calendar.leapdays(year1, year2)   

The arguments passed to the function are 2 valid year values. This function returns the number of leap years between those years.

Example:

calendar.leapdays(2000, 2017)   

Output:

5   

As seen, there are 5 leap years between 2000 and 2017, hence the output is 5.

Return the Day of a Week

The weekday method takes 3 arguments, namely: year, month, and day. The function returns the day of a week, with Monday having an index of 0 and Sunday having an index of 6. For example:

calendar.weekday(2019, 3, 21)   

Output:

3   

As seen, this function returns index value “3”, which is “Thursday”.

Getting Abbreviated Weekday Names

The function weekheader takes an argument n, which specifies the number of characters for a particular weekday name and returns a header containing abbreviated weekday names.

For example:

print (calendar.weekheader(2))   

Output:

Mo Tu We Th Fr Sa Su   

Similarly,

print (calendar.weekheader(3))   

Output:

Mon Tue Wed Thu Fri Sat Sun   

Getting Number of Days in a Month

The monthrange function takes 2 arguments: year and month. This function returns a tuple containing the index of the day of the week in which the month starts and the number of days in the month.

For example:

print (calendar.monthrange(1983, 12))   

Output:

{3,31} 

Since the first day of December, 1983 was a Thursday, the function returns index value of Thursday as the first element of the tuple, and 31 since that is the number of days in December.

Get the Weeks in a Month

The monthcalendar function takes 2 arguments: year and month and returns a matrix, in which each row represents a week in that month.

For example:

print(calendar.monthcalendar(1983, 11))   

Output:

[[0,1,2,3,4,5,6], [7,8,9,10,11,12,13], [14,15,16,17,18,19,20], [21,22,23,24,25,26,27], [28,19,30,0,0,0]] 

As you can see, each week array begins with Monday and days outside of the month are represented with zeroes. So the first array indicates that the first day of the month is a Tuesday.

Modifying Default Settings

Default calendar settings can be modified to fit your needs. For example, the following script sets Monday as the first day of the week.

class calendar.calendar(firstweekday=0)   

By default, calendars follow European convention, having Monday as the first day of the week and Sunday as the last day of the week. Also, the month January has the index value 1 and December has the index value 12.

Useful Methods of the Calendar Class

The following are some of the most useful methods of the calendar class.

The iterweekdays() Method

This method returns an iterator that contains a list of indexes for the days in a week.

For example:

import calendar  c = calendar.Calendar()   for i in c.iterweekdays():       print (i, end=" ") 

Output:

0 1 2 3 4 5 6   

The itermonthdates() Method

The itermonthdates() takes 2 arguments: year and month. This function returns an iterator of all days of the given month. Also, all days before the start of the month and after the end of the month, required to get the complete week, are displayed.

Example:

import calendar  c = calendar.Calendar()   for i in c.itermonthdates (2019, 1):       print (i, end=" ") 

Output:

2018-12-31 2019-01-01 2019-01-02 2019-01-03 ..............2019-02-03   

The itermonthdays() Method

This method is similar to itermonthdates method, but it only returns the day numbers.

Example:

import calendar  c = calendar.Calendar()   for i in c.itermonthdays (2019, 1):       print (i, end=" ") 

Output:

0 1 2 3 4 5 6........ 31 0 0 0   

As you can see, all days before the start of the month and after the end of the month to get the complete week are set to “0”.

The itermonthdays2() Method

This method displays a tuple consisting of day and weekday numbers.

Example:

import calendar  c = calendar.Calendar()   for i in c.itermonthdays2 (2019, 1):       print (i, end=" ") 

Output:

(0,0) (1,1) (2,2) (3,3) (4,4) (5,5) (6,6) (7,0) (8,1) (9,2) ........... 

The itermonthdays3() Method

This method is pretty similar to the itermonthdays3() method, except that it returns a tuple of year, month, and the day of the month.

Example:

import calendar  c = calendar.Calendar()   for i in c.itermonthdays3 (2019, 1):       print (i, end=" ") 

Output:

(2018,12,31) (2019,01,01) (2019,01,02).....(2019,01,31) (2019,02,01) (2019,02,02) (2019,02,03) 

The monthdatescalendar() Method

This method takes year and month as arguments and returns a list of full weeks in the month. Each week is a list of 7 datetime.date objects.

Example:

import calendar  c = calendar.Calendar()   for i in c.monthdatescalendar (2019, 1):       print (i, end=" ") 

Output:

[datetime.date(2018, 12, 31), datetime.date(2019, 01, 01), datetime.date(2019, 01, 02), datetime.date(2019, 01, 03), datetime.date(2019, 01, 04), datetime.date(2019, 01, 05), datetime.date(2019, 01, 06)... datetime.date(2019, 02, 03)] ..... 

The monthdays2calendar() Method

This function takes year and month as arguments and returns a list of weeks, with each week as 7 tuples of the day of month and day of the week.

Example:

import calendar  c = calendar.Calendar()   for i in c.monthdays2calendar (2019, 1):       print(i, end=" ") 

Output:

[(0,0) (1,1) (2,2) (3,3) (4,4) (5,5) (6,6)] [(7,0) (8,1) (9,2) (10,3) (11,4) (12,5) (13,6)] .... 

As you see, the first value of the tuple is the day of the month (0-31) and second value of the tuple is the week number (0-6)

The monthdayscalendar() Method

This method takes year and month as arguments and returns a list of full weeks, with each week being a list of days of a month.

Example:

import calendar  c = calendar.Calendar()   for i in c.monthdayscalendar(2019, 1):       print (i, end=" ") 

Sample Output:

[0, 1, 2 , 3, 4, 5, 6] [7, 8, 9, 10, 11, 12, 13]....[28, 29, 30, 31, 0, 0, 0] 

The yeardatescalendar() Method

This function takes the year (yyyy) and the number of months in a month row (w). By default, w parameter is 3. The function returns a list of month rows, where days are datetime.date objects.

Example:

import calendar  c = calendar.Calendar()   for i in c.yeardatescalendar(2019, 3):       print (i, end=" ") 

Output:

[[[datetime.date(2018, 12, 31), datetime.date(2019, 1, 1), datetime.date(2019, 1, 2), datetime.date(2019, 1, 3), datetime.date(2019, 1, 4), datetime.date(2019, 1, 5), datetime.date(2019, 1, 6)], [datetime.date(2019, 1, 7), datetime.date(2019, 1, 8), datetime.date(2019, 1, 9), datetime.date(2019, 1, 10), datetime.date(2019, 1, 11), datetime.date(2019, 1, 12), datetime.date(2019, 1, 13)], [datetime.date(2019, 1, 14), datetime.date(2019, 1, 15), datetime.date(2019, 1, 16), datetime.date(2019, 1, 17), datetime.date(2019, 1, 18), datetime.date(2019, 1, 19), datetime.date(2019, 1, 20)], [datetime.date(2019, 1, 21), datetime.date(2019, 1, 22), datetime.date(2019, 1, 23), datetime.date(2019, 1, 24), datetime.date(2019, 1, 25), datetime.date(2019, 1, 26), datetime.date(2019, 1, 27)], [datetime.date(2019, 1, 28), datetime.date(2019, 1, 29), datetime.date(2019, 1, 30), datetime.date(2019, 1, 31), datetime.date(2019, 2, 1), datetime.date(2019, 2, 2), datetime.date(2019, 2, 3)]] ... ] 

The yeardays2calendar() Method

This function takes the year (yyyy) and number of months we want in a month row (w). By default, the w parameter is 3. The function returns a list of weeks, as tuples of days of the month and the day of the week.

Example:

import calendar  c = calendar.Calendar()   for i in c.yeardays2calendar(2019, 3):       print (i, end=" ") 

Output:

[[[(0, 0), (1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6)], [(7, 0), (8, 1), (9, 2), (10, 3), (11, 4), (12, 5), (13, 6)], [(14, 0), (15, 1), (16, 2), (17, 3), (18, 4), (19, 5), (20, 6)], [(21, 0), (22, 1), (23, 2), (24, 3), (25, 4), (26, 5), (27, 6)], [(28, 0), (29, 1), (30, 2), (31, 3), (0, 4), (0, 5), (0, 6)]], [[(0, 0), (0, 1), (0, 2), (0, 3), (1, 4), (2, 5), (3, 6)], [(4, 0), (5, 1), (6, 2), (7, 3), (8, 4), (9, 5), (10, 6)], [(11, 0), (12, 1), (13, 2), (14, 3), (15, 4), (16, 5), (17, 6)], [(18, 0), (19, 1), (20, 2), (21, 3), (22, 4), (23, 5), (24, 6)], [(25, 0), (26, 1), (27, 2), (28, 3), (0, 4), (0, 5), (0, 6)]], [[(0, 0), (0, 1), (0, 2), (0, 3), (1, 4), (2, 5), (3, 6)] ... ]] 

The yeardayscalendar() Method

This function takes the year (yyyy) and the number of months we want in a month row (w). By default, w parameter is 3. The function returns a list of weeks as the day of the month.

Example:

import calendar  c = calendar.Calendar()   for i in c.yeardayscalendar(2019, 3):       print (i, end=" ") 

Output:

[[[0, 1, 2, 3, 4, 5, 6], [7, 8, 9, 10, 11, 12, 13], [14, 15, 16, 17, 18, 19, 20], [21, 22, 23, 24, 25, 26, 27], [28, 29, 30, 31, 0, 0, 0]], [[0, 0, 0, 0, 1, 2, 3], [4, 5, 6, 7, 8, 9, 10], [11, 12, 13, 14, 15, 16, 17], [18, 19, 20, 21, 22, 23, 24], [25, 26, 27, 28, 0, 0, 0]], [[0, 0, 0, 0, 1, 2, 3], [4, 5, 6, 7, 8, 9, 10], [11, 12, 13, 14, 15, 16, 17], [18, 19, 20, 21, 22, 23, 24], [25, 26, 27, 28, 29, 30, 31]]] [[[1, 2, 3, 4, 5, 6, 7], [8, 9, 10, 11, 12, 13, 14], [15, 16, 17, 18, 19, 20, 21], [22, 23, 24, 25, 26, 27, 28], [29, 30, 0, 0, 0, 0, 0]] ... ]] 

The TextCalendar Class

The TextCalendar is used to generate plain text calendars. Similar to the Calendar class. This class takes a constructor where the first weekday is set to 0, by default. Let’s look at the methods provided by the TextCalendar class.

The formatmonth() Method

This method takes 4 arguments namely: year, month, the width of days column (w), and a number of lines used by each week (l). This method returns a multi-line string.

Example:

import calendar  c = calendar.TextCalendar()   print(c.formatmonth(2019, 1))   

This displays calendar of January, 2019.

Output:

    January 2019 Mo Tu We Th Fr Sa Su       1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20   21 22 23 24 25 26 27   28 29 30 31   

The prmonth() Method:

This method prints a month’s calendar as returned by the formatmonth method. We can use this function to avoid the use of “print” function, to print the calendar on the terminal.

To print the January, 2019 calendar, use:

c.prmonth(2019, 1)   

The formatyear() Method:

This method returns a “m” column calendar for the entire year. The arguments passed to this function are year (yyyy), date column width (w), number of lines per week (l), number of spaces between month’s column (c), number of columns (m).

The LocaleTextCalendar class:

This is a sub-class of TextCalendar class. Its constructor takes an additional argument, locale. It will return month and weekday names, in the specified locale. We can create a text calendar object in our native language. We can fetch month or weekdays or other data to display calendar formatted from the local system, other than the current default one. Example:

import calendar  for name in calendar.month_name:       print(name) 

This will print the name of the months, as per the local system.

Output:

January   February   March   April   May   June   July   August   September   October   November   December   

The HTMLCalendar Class:

This is similar to TextCalendar class, but, generates an HTML calendar. The constructor for this class has the firstweekday set to “0”.

Below are some of the methods provided by the HTMLCalendar class.

The formatmonth() method:

This function displays the calendar of a month, in a HTML table format. We can display April, 2019 calendar as a HTML table, using:

hc = calendar.HTMLCalendar()   print(hc.formatmonth(2019, 4))   

Output:

<table border="0" cellpadding="0" cellspacing="0" class="month">   <tr><th colspan="7" class="month">April 2019</th></tr>   <tr><th class="mon">Mon</th><th class="tue">Tue</th><th class="wed">Wed</th><th class="thu">Thu</th><th class="fri">Fri</th><th class="sat">Sat</th><th class="sun">Sun</th></tr>   <tr><td class="mon">1</td><td class="tue">2</td><td class="wed">3</td><td class="thu">4</td><td class="fri">5</td><td class="sat">6</td><td class="sun">7</td></tr>   <tr><td class="mon">8</td><td class="tue">9</td><td class="wed">10</td><td class="thu">11</td><td class="fri">12</td><td class="sat">13</td><td class="sun">14</td></tr>   <tr><td class="mon">15</td><td class="tue">16</td><td class="wed">17</td><td class="thu">18</td><td class="fri">19</td><td class="sat">20</td><td class="sun">21</td></tr>   <tr><td class="mon">22</td><td class="tue">23</td><td class="wed">24</td><td class="thu">25</td><td class="fri">26</td><td class="sat">27</td><td class="sun">28</td></tr>   <tr><td class="mon">29</td><td class="tue">30</td><td class="noday">&nbsp;</td><td class="noday">&nbsp;</td><td class="noday">&nbsp;</td><td class="noday">&nbsp;</td><td class="noday">&nbsp;</td></tr>   </table>   

The formatyear() method:

This method takes year and number of months in a row (w) as arguments and prints the entire year’s calendar as an HTML table. By default, the width is set to 3. We can display 2019 calendar as a HTML table using:

hc = calendar.HTMLCalendar()   print(hc.formatyear(2019, 4))   

The formatyearpage() method:

This method takes a year, number of months in a row (w), cascading style sheet (CSS), and encoding, as arguments. The css and encoding arguments can be set to None, in case we do not use CSS and encoding. This function displays an entire year’s calendar as an HTML page having default width of 3. We can print 2019 year’s calendar as a HTML page using:

hc = calendar.HTMLCalendar()   print(hc.formatyearpage(2019, 3, css=None, encoding=None))   
b'<?xml version="1.0" encoding="utf-8"?>\n<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n<html>\n<head>\n<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />\n<title>Calendar for 2019</title>\n</head>\n<body>\n<table border="0" cellpadding="0" cellspacing="0" class="year">\n<tr><th colspan="3" class="year">2019</th></tr><tr><td><table border="0" cellpadding="0" cellspacing="0" class="month">\n<tr><th colspan="7" class="month">January</th></tr>\n<tr><th class="mon">Mon</th><th class="tue">Tue</th><th class="wed">Wed</th><th class="thu">Thu</th><th class="fri">Fri</th><th class="sat">Sat</th><th class="sun">Sun</th></tr>\n<tr><td class="noday">&nbsp;</td><td class="tue">1</td><td class="wed">2</td><td class="thu">3</td><td class="fri">4</td><td class="sat">5</td><td class="sun">6</td></tr> ... </table></body>\n</html>\n'   

The HTMLCalendar output looks similar to the plain text version, but it is wrapped with HTML tags. The cell of the HTML table contains a class attribute corresponding to the day of the week. Therefore, the HTML calendar can be styled through CSS.

The LocaleHTMLCalendar Class

This is a subclass of the HTMLCalendar class. Its constructor takes an additional argument, locale. It will return month and weekday names, in the specified locale as an HTML table. We can create a text calendar object in our native language. For example, we can generate the April 2019 calendar as an HTML table in ‘en_AU’ locale using:

import calendar  cal = calendar.LocaleHTMLCalendar(locale='en_AU.utf8')   print(cal.formatmonth(2019, 4))   

Output:

<table border="0" cellpadding="0" cellspacing="0" class="month">   <tr><th colspan="7" class="month">April 2019</th></tr>   <tr><th class="mon">Mon</th><th class="tue">Tue</th><th class="wed">Wed</th><th class="thu">Thu</th><th class="fri">Fri</th><th class="sat">Sat</th><th class="sun">Sun</th></tr>   <tr><td class="mon">1</td><td class="tue">2</td><td class="wed">3</td><td class="thu">4</td><td class="fri">5</td><td class="sat">6</td><td class="sun">7</td></tr>   <tr><td class="mon">8</td><td class="tue">9</td><td class="wed">10</td><td class="thu">11</td><td class="fri">12</td><td class="sat">13</td><td class="sun">14</td></tr>   <tr><td class="mon">15</td><td class="tue">16</td><td class="wed">17</td><td class="thu">18</td><td class="fri">19</td><td class="sat">20</td><td class="sun">21</td></tr>   <tr><td class="mon">22</td><td class="tue">23</td><td class="wed">24</td><td class="thu">25</td><td class="fri">26</td><td class="sat">27</td><td class="sun">28</td></tr>   <tr><td class="mon">29</td><td class="tue">30</td><td class="noday">&nbsp;</td><td class="noday">&nbsp;</td><td class="noday">&nbsp;</td><td class="noday">&nbsp;</td><td class="noday">&nbsp;</td></tr>   </table>   

Conclusion

In this tutorial, we discussed the use of different classes and sub-classes of Calendar module in Python for working with dates to manage week/month/year oriented values. We also discussed the use of functions in the Python Calendar module. Along with this, we also implemented the TextCalendar and HTMLCalendar classes to produce pre-formatted output. I hope the tutorial was informative!

Planet Python

MacADUK 2019 Presentation Video online

The recording of my MacADUK 2019 presentation: “Modern Deployment Workflows for Business” is online and available:

You can find this video and the other recorded presentations in the MacADUK 2019 playlist on YouTube.

I am really happy with how this presentation turned out and I could be more happy with the keyframe that was chosen for the video.

You can find the notes and slides at the session’s permanent page.

Many thanks again to all those who put a lot of effort into making MacADUK 2019 the great conference it was. Also thanks to the attendees with all their great feedback and applause. See you all again next year!

Scripting OS X