Quantcast
Channel: Planet Python
Viewing all 22645 articles
Browse latest View live

Marc Richter: Create your own Telegram bot with Django on Heroku – Part 5 – Introduce Heroku

$
0
0

In the previous part of this series, I tried to explain the differences in the two modes a Telegram bot supports: pull (getUpdates) and push (Webhook) methods. I also explained what a webhook is and how to easily giving it a test-drive, without any hassle.

Today we will talk about what Heroku is and about how to prepare your workstation to effectively work with that platform.

If you do not like Heroku or if you simply want to use another hosting service for your bot like AWS, OpenShift, Google Cloud Platform, whatever for any reason: That is perfectly OK and you can do so! I will write this and the next article on Heroku in a way that even when you decide to skip it completely, you won’t miss anything from this Telegram/Python/Django article series but the presentation of how to get the bot hosted on Heroku.
The only thing which you won’t be able to follow 1:1 in the upcoming parts of this series is that you can’t use those commands to trigger new deployments to the hosting platform with or do some minor, hosting platform related steps. But if you are advanced and familiar enough with deploying web applications to have a better idea of hosting an application than me, this shouldn’t be much of a problem for you, since most commands for Heroku are pretty straightforward and easy to adapt for a different system.

What is Heroku and why do you advertise it here?

Heroku Logo

Heroku describes itself like this:

Heroku is a cloud platform that lets companies build, deliver, monitor and scale apps — we’re the fastest way to go from idea to URL, bypassing all those infrastructure headaches.

And yes: This is what they do! They are offering privates, entrepreneurs or even large enterprises a great platform to life-cycle, host and scale their applications.

This describes pretty much all cloud-based services out there nowadays, so again: Why Heroku?
Having tried some classical and recent services already, my experiences, in a nutshell, are like this:

Heroku offers an awesome and unmet level of features, low learning curve, cost-effectiveness and a great user experience. I’m absolutely in love with this platform and every time I’m working with it, it feels like “agile cloud-based software life-cycle platform done right“. To me, it gives a feeling of putting the often utilized term of cloud-computing to an actual use, without over-complicating the setup and gory details too much for someone who is not really interested in what is ticking under the hood.

I’m in no way related to Heroku! I do not work for them, I do not receive any advertising money to write about them, nor did they grab my first-born to force me to do advertisements for them.
I’m just convinced by it. And since their service and hosting is for free for a project of the scope of a Telegram bot like we are up to create in this article series, this finally makes it the perfect choice for this article series! 👍 Plus, it offers already HTTPS termination out-of-the-box, which is important for a Telegram bot.

OK, let’s give it a try then 🤩

Hooked? Great!

To get yourself started, you need to sign up for a free account on Heroku first. Just with most services, you will be asked for the common details: Email, Name, I’m not a robot, et cetera. After you have done that and confirmed your Email and so on, you can log in and you will be redirected to the dashboard, which is an overview of all your projects. Not very surprisingly, this will be empty.
But the fact that you can log in to the Dashboard successfully proves that everything is good to go!

Next, install the Heroku CLI. Heroku CLI is a command line interface which lets you manage and control your Heroku apps from the local command line and lets you do a lot of cool stuff; you can’t fully utilize the power of Heroku without it.
To install it, follow the instructions for your operating system from https://devcenter.heroku.com/articles/heroku-cli. As soon as the following command works on your system, you are all set up and good to continue reading this article:

~ $ heroku version
heroku/7.9.3 linux-x64 node-v10.9.0
~ $

A bit of terminology and concept

In Heroku lingua, the containers one can deploy to fulfill the typical container abstracted tasks are called “dynos“. Just with any container-centric platform nowadays, there can be different types of dynos like for example web, application, etc. Each one can be of different levels of performance, available RAM, can be scaled horizontally (concurrent dynos) and so on.

By default, everyone is on the “free” plan (for an overview of available plans, see pricing table). Without adding a credit card to charge for eventual costs, a free plan includes 550 “free dyno hours“. A dyno hour is a cost management entity which is consumed by active dynos. One dyno hour can be consumed by having one active dyno for one hour or two active dynos for 30 minutes, and so on. Whenever one of the following conditions matches, dyno hours are consumed:

  • There is a web dyno that is receiving traffic.
  • If a worker dyno is running without a web dyno.
  • A one-off dyno is running. (More on “one-off dynos” later or you read the Heroku article about it).

One month has up to 744 hours (31 * 24). If you are deciding not to register a payment method and go with the 550 free dyno hours, you should be fine to operate a bot 24/7 every month, anyways.

… is he stupid or what? 550 < 744 !

Yes, you are correct, that seems to not make much sense at first! But to keep an app up and running 24/7 on Heroku does not necessarily mean that the same amount of dyno hours is consumed:
The free plan lets you launch free web dynos. If a web dyno receives no traffic for 30 minutes (thus: No message is sent to and forwarded to the webhook by your bot), that web dyno will “fall asleep”. This can’t be compared to a standby- or hibernate mode of virtual machines: Dynos which are asleep will still answer and react to any request which is sent to them since the socket stays open and is listened to, but if a request is received, there will be a short delay (~30 seconds) during which the dynos are re-activated in the background automatically and stay up for another 30 minutes before it will fall asleep again. Sleeping dynos are not consuming any dyno hours. This way my personal bot (which is in active use by two people) usually does not consume more than 50 dyno hours of the 1000 free dyno hours per month (I have added payment details) even though it is available 24/7.

So: If you have either the need for immediate reaction of your dynos without a delay of ~30 seconds ever, you will need to upgrade your plan to the “hobby” plan ($7 per dyno/month). If you can live with this delay for each first request in a 30 minute time frame (the subsequent requests will not have this delay) and do not keep it active 24/7 really, you do not only not need to upgrade your plan but you should not even need those additional 450 dyno hours by adding payment details as well and should even have enough dyno hours spare to add several additional projects to your account.

Authenticate your CLI to your Heroku account

By now, you should already have an activated Heroku account and the Heroku CLI installed on your workstation.
Now, we will authenticate the CLI to the platform, using your account. This enables you to fully get rid of the Web-based dashboard for most things and only operate on clear, reproducible command line segments. Even though the web browser and the web-based dashboard is not needed from this point onwards anymore, I still recommend to browse it and investigate the results of your command line actions, just to get a better idea about what you really did and trigger with it, since often looking at the fine polished web GUI is less abstract and more easy to understand for some people.

To login to your account, run the following command:

~ $ heroku login
heroku: Enter your login credentials
Email: ****@marc-richter.info
Password: **********************
Logged in as ****@marc-richter.info
~ $

This registers a new API token in your Heroku account (see https://dashboard.heroku.com/account) and add that token to your 

~/.netrc
 file.

That’s it! You can now start using 

heroku
 command to control your apps!

For additional details on the Heroku CLI Authentication, please consult the Heroku article on authentication.

Prepare a new dummy project folder to hold your future- and first code

We do not have any code yet, do we? Let’s create a new folder to hold our future application at 

~/tbot
and change into it. If you prefer another location, feel free to do so, but please adopt that in the following occurrences of 
~/tbot
accordingly.

Let’s initialize a Git repository in it already, since 

heroku
 command can add it’s remote repository URL to it then automatically and save us this manual step:

~/tbot $ git init
Initialized empty Git repository in /home/testuser/tbot/.git/
~/tbot $

I decided to give an example of this using Flask since it is a bit more lightweight for this minimal

Hello World!
 – application I’m about to show here and I will not accidentally share something Django related which I either miss to repeat for those who decided to skip this Heroku-part, nor create duplicated content.

In this empty folder, we will start to create a Flask based application as if Heroku was not involved. Just do your usual stuff. Maybe create a virtualenv for it, install modules you need, etc. :

~/tbot $ mkvirtualenv -p python3.6 .
Running virtualenv with interpreter /usr/bin/python3.6
...
(tbot) ~/tbot $ pip install flask
Collecting flask
...
Successfully installed Jinja2-2.10 MarkupSafe-1.0 Werkzeug-0.14.1 click-6.7 flask-1.0.2 itsdangerous-0.24
(tbot) ~/tbot $

Create a “Hello World!” Flask-app 👋🌍

Let’s keep this easy and just take the official Flask-minimal example for this:

from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'Hello, World!'

Save this to the file 

hello.py
 .

Next, let’s give this a local try:

(tbot) ~/tbot $ export FLASK_APP=hello.py
(tbot) ~/tbot $ flask run
 * Serving Flask app "hello.py"
 * Environment: production
   WARNING: Do not use the development server in a production environment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

When you navigate your browser to

http://127.0.0.1:5000/
 , you should see “Hello, World!” in it.
If this works, we can start to prepare a deployment to Heroku for it.

Create an app on Heroku

To deploy any code to Heroku, we need to create an app for that. To do so, we will use the freshly authenticated Heroku CLI. To have your app created successfully, it needs a name which is exclusive in whole Heroku. Something generic like “telegram-bot” will most certainly fail, though. If you are not distracted from a cryptic name, just skip the optional name parameter:

~ $ heroku create
Creating app... done, ⬢ nameless-garden-54450
https://nameless-garden-54450.herokuapp.com/ | https://git.heroku.com/nameless-garden-54450.git
~ $

As you can see in this example, a random name is generated for your app if you do not provide a name for it. In this case, my app received the name

nameless-garden-54450
.
Another interesting thing in this output: A SSL terminated URL was registered (
https://nameless-garden-54450.herokuapp.com/
) and a Git repository was created (
https://git.heroku.com/nameless-garden-54450.git
) for the new app automatically. Both can be visited or used immediately. The app URL will show a dummy page until you add the first content to your app:

Heroku dummy page

This should also have added a new remote to your Git repository, called

heroku
, pointing to the Heroku hosted Git repo for your app:

(tbot) ~/tbot $ git remote -v
heroku  https://git.heroku.com/nameless-garden-54450.git (fetch)
heroku  https://git.heroku.com/nameless-garden-54450.git (push)
(tbot) ~/tbot $

Prepare your deployment

We will provide our app with gunicorn. Let’s install that, first. After that, create a 

requirements.txt
 – file with your current module list, since this is one of the expected files for Heroku Python buildpack:

(tbot) ~/tbot $ pip install gunicorn
Collecting gunicorn
  Downloading https://files.pythonhosted.org/packages/8c/da/b8dd8deb741bff556db53902d4706774c8e1e67265f69528c14c003644e6/gunicorn-19.9.0-py2.py3-none-any.whl (112kB)
    100% |████████████████████████████████| 122kB 3.4MB/s 
Installing collected packages: gunicorn
Successfully installed gunicorn-19.9.0
(tbot) ~/tbot $ pip freeze > requirements.txt 
(tbot) ~/tbot $

Again, let’s test that locally first:

(tbot) ~/tbot $ gunicorn hello:app
[2018-08-22 13:45:40 +0000] [12046] [INFO] Starting gunicorn 19.9.0
[2018-08-22 13:45:40 +0000] [12046] [INFO] Listening at: http://127.0.0.1:8000 (12046)
[2018-08-22 13:45:40 +0000] [12046] [INFO] Using worker: sync
[2018-08-22 13:45:40 +0000] [12050] [INFO] Booting worker with pid: 12050

Sweet! This works also.

Next, we need to create a 

Procfile
 . This is a simple file which tells Heroku how to launch your app. For our simple example, we just need the following line:

web: gunicorn hello:app

Once this file was created, let’s test it one last time locally, before we deploy it to Heroku. This time, we will utilize 

heroku local
 command. This starts your app locally, just like it will be done remotely once we have deployed it:

(tbot) ~/tbot $ heroku local
[WARN] No ENV file found
13:50:24 web.1   |  [2018-08-22 13:50:24 +0000] [12386] [INFO] Starting gunicorn 19.9.0
13:50:24 web.1   |  [2018-08-22 13:50:24 +0000] [12386] [INFO] Listening at: http://0.0.0.0:5000 (12386)
13:50:24 web.1   |  [2018-08-22 13:50:24 +0000] [12386] [INFO] Using worker: sync
13:50:24 web.1   |  [2018-08-22 13:50:24 +0000] [12390] [INFO] Booting worker with pid: 12390

If this is also working, you should be ready to deploy!
Let’s do one last, optional step: To prevent the 

.pyc
 – files to be added to the repo, let’s create a 
.gitignore
 – file for these before we start to add or commit anything: 
echo '*.pyc' > .gitignore

Ship it! 🛳

If you followed all the previous steps, your project folder should look similar to this by now:

(tbot) ~/tbot $ ls -1a
.
..
.git
.gitignore
hello.py
hello.pyc
Procfile
requirements.txt
(tbot) ~/tbot $

You can now add and commit these files to your Git repository:

(tbot) ~/tbot $ git status
On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   .gitignore
    modified:   Procfile
    modified:   hello.py
    modified:   requirements.txt

no changes added to commit (use "git add" and/or "git commit -a")
(tbot) ~/tbot $ git add .
(tbot) ~/tbot $ git commit -m "Init"
[master 8dd2ecb] Init
 4 files changed, 1 insertion(+), 3 deletions(-)
(tbot) ~/tbot $

To deploy everything to Heroku, you are using Git pushes. That’s it! From now on, you can already forget about any hosting details and simply focus on programming.
To deploy this “Hello World!” – application, you need to push your commits to the “heroku” Git remote, which got added during app creation, automatically. You will see a build- and deployment-output while your app is prepared:

(tbot) ~/tbot $ git push heroku master
Zähle Objekte: 9, Fertig.
Delta compression using up to 4 threads.
Komprimiere Objekte: 100% (7/7), Fertig.
Schreibe Objekte: 100% (9/9), 819 bytes | 0 bytes/s, Fertig.
Total 9 (delta 2), reused 0 (delta 0)
remote: Compressing source files... done.
remote: Building source:
remote: 
remote: -----> Python app detected
remote: -----> Installing python-3.6.6
remote: -----> Installing pip
remote: -----> Installing SQLite3
remote: -----> Installing requirements with pip
remote:        Collecting click==6.7 (from -r /tmp/build_8da096c58d0ca30c079c4642ebdd08fb/requirements.txt (line 1))
remote:          Downloading https://files.pythonhosted.org/packages/34/c1/8806f99713ddb993c5366c362b2f908f18269f8d792aff1abfd700775a77/click-6.7-py2.py3-none-any.whl (71kB)
remote:        Collecting Flask==1.0.2 (from -r /tmp/build_8da096c58d0ca30c079c4642ebdd08fb/requirements.txt (line 2))
remote:          Downloading https://files.pythonhosted.org/packages/7f/e7/08578774ed4536d3242b14dacb4696386634607af824ea997202cd0edb4b/Flask-1.0.2-py2.py3-none-any.whl (91kB)
remote:        Collecting gunicorn==19.9.0 (from -r /tmp/build_8da096c58d0ca30c079c4642ebdd08fb/requirements.txt (line 3))
remote:          Downloading https://files.pythonhosted.org/packages/8c/da/b8dd8deb741bff556db53902d4706774c8e1e67265f69528c14c003644e6/gunicorn-19.9.0-py2.py3-none-any.whl (112kB)
remote:        Collecting itsdangerous==0.24 (from -r /tmp/build_8da096c58d0ca30c079c4642ebdd08fb/requirements.txt (line 4))
remote:          Downloading https://files.pythonhosted.org/packages/dc/b4/a60bcdba945c00f6d608d8975131ab3f25b22f2bcfe1dab221165194b2d4/itsdangerous-0.24.tar.gz (46kB)
remote:        Collecting Jinja2==2.10 (from -r /tmp/build_8da096c58d0ca30c079c4642ebdd08fb/requirements.txt (line 5))
remote:          Downloading https://files.pythonhosted.org/packages/7f/ff/ae64bacdfc95f27a016a7bed8e8686763ba4d277a78ca76f32659220a731/Jinja2-2.10-py2.py3-none-any.whl (126kB)
remote:        Collecting MarkupSafe==1.0 (from -r /tmp/build_8da096c58d0ca30c079c4642ebdd08fb/requirements.txt (line 6))
remote:          Downloading https://files.pythonhosted.org/packages/4d/de/32d741db316d8fdb7680822dd37001ef7a448255de9699ab4bfcbdf4172b/MarkupSafe-1.0.tar.gz
remote:        Collecting Werkzeug==0.14.1 (from -r /tmp/build_8da096c58d0ca30c079c4642ebdd08fb/requirements.txt (line 7))
remote:          Downloading https://files.pythonhosted.org/packages/20/c4/12e3e56473e52375aa29c4764e70d1b8f3efa6682bef8d0aae04fe335243/Werkzeug-0.14.1-py2.py3-none-any.whl (322kB)
remote:        Installing collected packages: click, Werkzeug, MarkupSafe, Jinja2, itsdangerous, Flask, gunicorn
remote:          Running setup.py install for MarkupSafe: started
remote:            Running setup.py install for MarkupSafe: finished with status 'done'
remote:          Running setup.py install for itsdangerous: started
remote:            Running setup.py install for itsdangerous: finished with status 'done'
remote:        Successfully installed Flask-1.0.2 Jinja2-2.10 MarkupSafe-1.0 Werkzeug-0.14.1 click-6.7 gunicorn-19.9.0 itsdangerous-0.24
remote: 
remote: -----> Discovering process types
remote:        Procfile declares types -> web
remote: 
remote: -----> Compressing...
remote:        Done: 42M
remote: -----> Launching...
remote:        Released v3
remote:        https://nameless-garden-54450.herokuapp.com/ deployed to Heroku
remote: 
remote: Verifying deploy... done.
To https://git.heroku.com/nameless-garden-54450.git
 * [new branch]      master -> master
(tbot) ~/tbot $

Well, that’s it! When you navigate to the unique URL of your app now, you should see the same 

Hello, World!
 like you did when you tested locally before:

heroku hello world

It works like a charm! Did you notice we did not even define if we want to build a PHP or Python app yet? Since Heroku has some implemented logic to determine the correct buildpack to run your application, we do not need to define a buildpack manually with this code. A “buildpack” is a script-collection which defines how to prepare and deploy an app and make it running. You can have a look on the buildpacks by browsing Heroku’s Github account and look out for “heroku-buildpack-*” labeled repositories. For example, the one for Python is located here: https://github.com/heroku/heroku-buildpack-python.
If you want to learn more about Heroku’s buildpacks, check out their buildpacks article.

Awesome, isn’t it? I’m thrilled by the simplicity of the system every time I kick off a new app.

There’s more …

I hope I could put fire on your enthusiasm for the platform with this article, too! There are tons of additional features to the platform, like scaling the count of dynos, configuring deployment plans, opening a shell inside the remote containers to do maintenance work like manage.py – tasks, et cetera, et cetera. But since this article has grown longer than I intended it to become, I will do that either “on the fly” during the following parts or write another article with advanced Heroku – topics.
For now, what you have learned during this article gives you just enough knowledge and skill to follow the remaining articles to get your Telegram bot up and running.

Outlook for the next part of the series

We just learned what Heroku is and how one can get started from zero to create and deploy a web-based Flask application and make it available to the world wide web.

In the next article of this series, we will create another application. This time, it will be Django and we will kick it off exactly like we did with this Flask based one and deploy it to Heroku, to make it available as a webhook for your Telegram bot.

If you liked or disliked this article, I’d love to read that in the comments!

Enjoy coding folks!

Born in 1982, Marc Richter is an IT enthusiastic since 1994. He became addicted when he first put hands on their family’s pc and never stopped investigating and exploring new things since then.
He is married to Jennifer Richter and proud father of two wonderful children, Lotta and Linus.
His current professional focus is DevOps and Python development.

An exhaustive bio can be found at this blog post.

Found my articles useful? Maybe you would like to support my efforts and give me a tip then?

Der Beitrag Create your own Telegram bot with Django on Heroku – Part 5 – Introduce Heroku erschien zuerst auf Marc Richter's personal site.


Codementor: Let's make Masonite Framework and Laravel Mix work together

$
0
0
Masonite (https://github.com/MasoniteFramework/masonite) is a beautifully crafted Web Framework for Python. We usually use files like CSS, JavaScript and image files known as Web assets to make our...

PyCharm: PyCharm 2018.2.3 RC

$
0
0

PyCharm 2018.2.3 Release Candidate is now available, with some small improvements. Get it now from our Confluence page

New in This Version

  • A number of improvements and fixes for the integrated Python console
  • Fixes for the new Quick Documentation window which was new in Pycharm 2018.2
  • Fixes for few false positives: install an already existing package intention, type checks
  • And much more, including improvements from the IntelliJ platform, WebStorm, and DataGrip, see the release notes for details.

Interested?

Download the RC from our confluence page

If you’re on Ubuntu 16.04 or later, you can use snap to get PyCharm RC versions, and stay up to date. You can find the installation instructions on our website.

The release candidate (RC) is not an early access program (EAP) build, and does not bundle an EAP license. If you get PyCharm Professional Edition RC, you will either need a currently active PyCharm subscription, or you will receive a 30-day free trial.

py.CheckIO: How to write secure code in Python

$
0
0
secure python code

Nowadays information has become as valuable as oil in the 20th century or gold in the Middle Ages. Accordingly, there are some people out there who seek to take hold of it, often using illegal methods, like hackers. However, it would be much more difficult for them to access classified information if program developers didn’t leave loopholes and vulnerabilities in the code (unintentionally, of course). In this article you’ll be to learn about the most common code security vulnerabilities, as well as read about the ways to avoid them and make your code safer.

Python Software Foundation: PyCon 2019 - Call for Sponsors

$
0
0
It seems like PyCon 2018 was just last week, but the 2019 edition will be here before we know it. We want to say thanks to all our sponsors who helped make the conference a huge success. This year, we welcomed 3,260 attendees from 48 different countries around the world, strengthening the connection in our beloved community even more. 
The invaluable and generous support of our PyCon sponsors enables the Python Software Foundation to help and improve the Python community worldwide by promoting sprints, meetups, events, projects, fiscal sponsorships, software development, open source projects and the Python Ambassador Program – which helps the creation of communities where Python is not well known. 
PyCon sponsorship enabled us to award $118,543 USD in financial aid to 143 attendees in 2018. It also generates 80% of the PSF's revenue, making financial aid, conferences, workshops, and training support possible. As a result, in 2017 $271,138 was awarded to grant recipients in 34 different countries and we are on track to meet or beat our total from last year.
Your sponsorship helps keep PyCon affordable and accessible to the widest possible audience. 
Here is a sample of the many benefits from being a sponsor:
  • Being part of the biggest Python conference in the world
  • Visibility to those who could potentially become new customers or employees
  • Increasing your brand exposure and elevating your corporate identity within the community
  • Expose your products to more than 3,200 attendees
  • Enhance your company’s reputation by supporting and investing in Python and the open source community
Depending on your level of sponsorship, packages may include complimentary conference passes, booth space, lead retrieval scanners, speaking opportunities, and participation in the Job Fair. Our current sponsorship prospectus can be found here. Sponsors in the Diamond, Platinum, Gold or Silver categories will receive additional tickets to the conference.
We want to hear from you! Contact us anytime - we are flexible and willing to build a sponsorship package that fits your needs. Only you know your business, how you measure success and what you're looking for. For more information please contact betsy@python.org or pycon-sponsors@python.org.
We proudly want to announce the organizations that are already sponsoring the PyCon 2019!
Huntington Convention Center - Cleveland, Ohio.
Photo Credit: Mike Pirnat https://www.flickr.com/photos/mikepirnat
PyCon 2019 will be held at Huntington Convention Center in Cleveland, Ohio, from May 1st to May 9th.
If you would like to share information about PyCon 2019 sponsorship, please share with this tweet:
Support @ThePSF by sponsoring @pycon 2019! More information can be found here: https://us.pycon.org/2019/sponsors/prospectus/. #pycon2019

PyCon 2018 Staff.
Photo Credit: Mike Pirnat https://www.flickr.com/photos/mikepirnat

Here’s what our attendees say about the PyCon US experience:

#PyCon2018 was my first PyCon. I have had an INCREDIBLE time! I've listened to inspirational speakers; met some of the most amazing people and have made lifelong connections. Most of all, I had FUN! Thanks to the brilliant @pycon team for working tirelessly to make it a reality!
- Julian Sequeira (@_juliansequeira)

PyCon has been my gold standard for conference accessibility as long as I've been attending, they do a great job and the community here really reflects it. I appreciate all your hard work @pycon, keep it up.
- Jonan Scheffler (@thejonanshow)
#pycon2018 was incredible. The support and hospitality from #Cleveland was stellar. Lighting all the downtown buildings in blue and yellow was a class act. I can’t wait to come back next year for some Mabel’s BBQ and the amazing @pycon community
- Jenn Basalone (@pennyblackio)
One of more understated benefits of @pycon is the economic and social impact in the surrounding communities it takes place at. In the case of larger cities, might not a big deal. In smaller locales, like Cleveland, that impact can be huge!
- Ruben Orduz (@rdodev)
Just got back from @pycon. Was delighted by the inclusiveness and thoughtfulness I saw there.
- David Vandegrift (@DavidVandegrift)


Rock and Roll Hall of Fame - Cleveland, Ohio
Photo Credit: Mike Pirnat https://www.flickr.com/photos/mikepirnat
PyCon is underwritten by the Python Software Foundation, a 501(c)3 charitable organization set up to manage the growth and development of Python worldwide.

PyCon: PyCon 2019 - Call for Sponsors

$
0
0
It seems like PyCon 2018 was just last week, but the 2019 edition will be here before we know it. We want to say thanks to all our sponsors who helped make the conference a huge success. This year, we welcomed 3,260 attendees from 48 different countries around the world, strengthening the connection in our beloved community even more. 
The invaluable and generous support of our PyCon sponsors enables the Python Software Foundation to help and improve the Python community worldwide by promoting sprints, meetups, events, projects, fiscal sponsorships, software development, open source projects and the Python Ambassador Program – which helps the creation of communities where Python is not well known. 
PyCon sponsorship enabled us to award $118,543 USD in financial aid to 143 attendees in 2018. It also generates 80% of the PSF's revenue, making financial aid, conferences, workshops, and training support possible. As a result, in 2017 $271,138 was awarded to grant recipients in 34 different countries and we are on track to meet or beat our total from last year.
Your sponsorship helps keep PyCon affordable and accessible to the widest possible audience. 
Here is a sample of the many benefits from being a sponsor:
  • Being part of the biggest Python conference in the world
  • Visibility to those who could potentially become new customers or employees
  • Increasing your brand exposure and elevating your corporate identity within the community
  • Expose your products to more than 3,200 attendees
  • Enhance your company’s reputation by supporting and investing in Python and the open source community
Depending on your level of sponsorship, packages may include complimentary conference passes, booth space, lead retrieval scanners, speaking opportunities, and participation in the Job Fair. Our current sponsorship prospectus can be found here. Sponsors in the Diamond, Platinum, Gold or Silver categories will receive additional tickets to the conference.
We want to hear from you! Contact us anytime - we are flexible and willing to build a sponsorship package that fits your needs. Only you know your business, how you measure success and what you're looking for. For more information please contact betsy@python.org or pycon-sponsors@python.org.
We proudly want to announce the organizations that are already sponsoring the PyCon 2019!
PyCon 2019 will be held at Huntington Convention Center in Cleveland, Ohio, from May 1st to May 9th.
Huntington Convention Center - Cleveland, Ohio
Photo Credit: Mike Pirnat https://www.flickr.com/photos/mikepirnat

If you would like to share information about PyCon 2019 sponsorship, please share with this tweet:
Support @ThePSF by sponsoring @pycon 2019! More information can be found here: https://us.pycon.org/2019/sponsors/prospectus/. #pycon2019


PyCon 2018 Staff
Photo Credit: Mike Pirnat https://www.flickr.com/photos/mikepirnat

Here’s what our attendees say about the PyCon US experience:


#PyCon2018 was my first PyCon. I have had an INCREDIBLE time! I've listened to inspirational speakers; met some of the most amazing people and have made lifelong connections. Most of all, I had FUN! Thanks to the brilliant @pycon team for working tirelessly to make it a reality!
- Julian Sequeira (@_juliansequeira)


PyCon has been my gold standard for conference accessibility as long as I've been attending, they do a great job and the community here really reflects it. I appreciate all your hard work @pycon, keep it up.
- Jonan Scheffler (@thejonanshow)



#pycon2018 was incredible. The support and hospitality from #Cleveland was stellar. Lighting all the downtown buildings in blue and yellow was a class act. I can’t wait to come back next year for some Mabel’s BBQ and the amazing @pycon community
- Jenn Basalone (@pennyblackio)



One of more understated benefits of @pycon is the economic and social impact in the surrounding communities it takes place at. In the case of larger cities, might not a big deal. In smaller locales, like Cleveland, that impact can be huge!
- Ruben Orduz (@rdodev)



Just got back from @pycon. Was delighted by the inclusiveness and thoughtfulness I saw there.
- David Vandegrift (@DavidVandegrift)
Rock and Roll Hall of Fame - Cleveland, Ohio
Photo Credit: Mike Pirnat https://www.flickr.com/photos/mikepirnat


PyCon is underwritten by the Python Software Foundation, a 501(c)3 charitable organization set up to manage the growth and development of Python worldwide.

EuroPython: EuroPython 2018: Videos for Wednesday available

$
0
0

We are pleased to announce the first batch of cut videos from EuroPython 2018 in Edinburgh, Scotland, UK.

image

EuroPython 2018 YouTube Playlist

In this batch, we have included all videos for Wednesday, July 25 2018, the first conference day.

In the coming two weeks we will publish videos for the next two conference days. In total, we will have more than 130 videos available for you to watch.

All EuroPython videos, including the ones from previous conferences, are available on our EuroPython YouTube Channel.

Enjoy,

EuroPython 2018 Team
https://ep2018.europython.eu/
https://www.europython-society.org/

Codementor: How and why I built a scraper for 3 local public bike systems

$
0
0
building a scraper for Santiago's 3 public bike systems

Kogan Dev: Faster Django Tests by Disabling Signals

$
0
0

Django signals are a form of Inversion Of Control that developers can use to trigger changes based (usually) on updates to models.

The canonical example is automatically creating a UserProfile when a User instance is created:

from django.conf import settings
from django.db.models.signals import post_save
from django.dispatch import receiver

@receiver(post_save, sender=settings.AUTH_USER_MODEL)
def post_save_receiver(sender, instance, created, **kwargs):
    UserProfile.objects.create(user=instance, ...)

Signals are useful for connecting your own behaviour to events that you might not have control over, such as those generated by the framework or libary code. In general, you should prefer other methods like overriding model save methods if you have the ability, as it makes code easier to reason about.

Signal receivers can sometimes be much more complex than creating a single object, and there can be many receivers for any given event, which multiply the time it takes to perform simple actions.

Tests and signals

Often you won't actually need your signals to execute when you're running your test suite, especially if you're creating and deleting many thousands of model instances. Disconnecting signals is tricky though, especially if the disconnect/reconnect logic can be stacked.

An easier, but more invasive method for suspending signal receivers is to check a global setting, and return early.

from django.conf import settings

@receiver(post_save, sender=MyModel)
def mymodel_post_save(sender, **kwargs):
    if settings.DEBUG:
        return
    work()

This has two drawbacks. Firstly, it's messy, and you need to remember to add the check to each receiver. Secondly, it depends on a specific setting that you might need to have turned off when running your tests, which makes it harder to test the actual receiver.

Selectively disabling signals

We can take this most recent idea of checking a setting, and wrap it up nicely in our own decorator. When running your tests, you can override the SUSPEND_SIGNALS setting per test method or class.

Here's a gist that does just that

import functools

from django.conf import settings
from django.dispatch import receiver

def suspendingreceiver(signal, **decorator_kwargs):
    def our_wrapper(func):
        @receiver(signal, **decorator_kwargs)
        @functools.wraps(func)
        def fake_receiver(sender, **kwargs):
            if settings.SUSPEND_SIGNALS:
                return
            return func(sender, **kwargs)
        return fake_receiver
    return our_wrapper

And using this decorator in your test suite is straightforward:

@override_settings(SUSPEND_SIGNALS=True)
class MyTestCase(TestCase):
    def test_method(self):
        Model.objects.create()  # post_save_receiver won't execute

@suspendingreceiver(post_save, sender=MyModel)
def mymodel_post_save(sender, **kwargs):
    work()

And just like that, we can skip signal receivers in our test suite as we like!

Codementor: My experience of cracking coding interviews

Blue Yonder Tech: Meet blueyonder.ai @ EuroSciPy & SRECon 2018

Codementor: Backup your AWS Redshift cluster properly and sleep better!

$
0
0
This article explains how to use AWS Lambda function to manage manual snapshots of Amazon Redshift cluster.

Kay Hayen: Nuitka this week #5

$
0
0

Goto Generators

Finished. Done. Finally.

Bechmarking was exciting. One program benchmark I had run in the past, was twice as fast than before, showing that the new implementation is indeed much faster, which is fantastic news.

Creating generator expressions and using them both got substantially faster and that is great.

It took me a fair amount of time to debug coroutines and asyncgen based on the new goto implementation. But the result is really good, and a fair amount of old bugs have been fixed. There always had been a segfault with asyncgen test that now has been eradicated.

One major observation is now, with only one C stack, debugging got a lot easier before, where context switches left much of the program state not reachable.

Benchmarks

Posted this one Twitter already:

Nuitka Speedcenter Builtin sum with generator

That one construct test has been a problem child, where Nuitka was slower than CPython 2.x, and very little faster than 3.x, and now with goto generators finally has become consistently faster.

I will explain what you see there in the next issue. The short version is that there is code, in which for one run, one line is used, and in another the other line is used, and then the "construct" is measure that way, by making the delta of the two. That construct performance is then compared between Python and Nuitka.

So if e.g. Nuitka is already better at looping, that won't influence the number of making that sum call with a generator expression.

The alternative line uses the generator expression, to make sure the construction time is not counted. To measure that, there is another construct test, that just creates it.

Nuitka Speedcenter Generator Expression Creation

This one shows that stable Nuitka was already faster at creating them, but that the develop version got even faster again. As creating generator objects became more lightweight, that is also news.

There are constructs for many parts of Python, to shed a light on how Nuitka fares for that particular one.

Holiday

In my 2 weeks holiday, I will try and focus on the next big thing, C types, something also started in the past, and where recent changes as part of the heap storage, should make it really a lot easier to get it finished. In fact I don't know right now, why my bool experimental work shouldn't just prove to be workable.

I am not going to post a TWN issue next week, mostly because my home servers won't be running, and the static site is rendered on one of them. Of course that would be movable, but I won't bother.

I am going to post a lot on Twitter though.

Static Compilation

There is a Github issue where I describe how pyenv on MacOS ought to be possible to use, and indeed, a brave soul has confirmed and even provided the concrete commands. All it takes now is somebody to fit this into the existing caching mechanism of Nuitka and to make sure the static library is properly patched to work with these commands.

Now is anyone of you going to create the code that will solve it for good?

Twitter

Follow me on twitter if you like, I will post important stuff as it happens there:

Follow @kayhayen

And lets not forget, having followers make me happy. So do re-tweets.

Hotfixes

And there have been yet again more hotfixes. Some are about coroutine and asyncgen corruptions for closes of frames. Multiprocessing plugin on Windows will work in all cases now.

Noteworthy was that the "0.5.32.6" was having a git merge problem on the cherry-pick that git didn't tell me about, leading to crashes. That made it necessary to push an update right after. I was confused that I didn't get a conflict, because there was one. But I am to blame for not checking the actual diff.

Bug Tracker

The next release will make Github the official tracker for Nuitka issues. I am working down the issues on the old tracker. The web site already pointed users there for a while, and I was set on this for some time, but yesterday I focused on taking action.

Basically what won me over is the easier templating of issues and pull requests that would have been possible with Roundup, but never happened. Also the OpenID integration that bugs.python.org has, never became available to me in a ready usable form.

Issue Backlog

Finishing goto "generators allowed" for around 10 issues to be closed alone, and I went over things, and checked out some stale issues, to see if they are dealt with, or pinging authors. I spent like half a day on this, bring down the issue count by a lot. Tedious work, but must be done too.

Also my inbox got a fair amount of cleanup, lots of issues pile up there, and from time to time, I do this, to get things straight. I raised issues for 2 things, that I won't be doing immediately.

But actually as issues go, there really very little problematic stuff open right now, and nothing important really. I would almost call it issue clean.

Help Wanted

If you are interested, I am tagging issues help wanted and there is a bunch, and very like one you can help with.

Nuitka definitely needs more people to work on it.

Plans

The goto generator work could be released, but I want to make the compile all the world test before I do so. It is running right now, but I will not complete before I leave. Also I do not want to get regression reports in my holiday, and goto generators along with heap storage, mean there could be some.

I am going to work on C types now. There is a few closing down actions on what I observed doing goto generators. There are a few easy ways to get even slightly better performance, definitely smaller code out of generators. Not sure if I go there first, or for the C types work directly. I often like to get these kind of observations dealt with more immediately, but I don't want to spend too much quality time on it.

Donations

As I have been asked this, yes, you can donate to Nuitka if you wish to further its development. Go here:

Donate to Nuitka

Marc Richter: Create your own Telegram bot with Django on Heroku – Part 6 – Creating the Django app

$
0
0

 

 

Django_Pony

In the previous part of this series, I tried to give you a brief yet thorough introduction to hosting your projects with Heroku.
That part was special because it was a completely optional part of this series; if you prefer to host your applications on a different platform and skipped that article, I’d like to repeat that this is completely OK and that I had shown nothing you will need for anything different but interacting with Heroku. You will hopefully notice no blank spots in the following articles. There is no need to read that article if you do not plan to use Heroku for hosting your bot. But you should be familiar enough with your hosting solution of choice to adopt the Heroku – commands I show here to an adequate setup for your hosting solution.

Today we will finally start creating our bot with Django. What we did up until now was just some kind of preparation and establishing background. In this part, we will finally start with the real stuff. 💪

Foreword

This article reflects how I created my first Django project. I had some experience with Python before already, but I never created any real Django project apart from the standard tutorial – setups, when one is trying to follow the official Django intro, for example.

When you follow this article, you will end up with a working Telegram bot which I have not found any issue with yet. But neither is it based on any best-practice on how to create Django applications nor will there be any conceptual groundbreaking insights. Maybe you could not even do worse than to follow my instructions when you are unfamiliar with Django and want to learn how it is done; maybe, I do not know better myself. But on the other hand I think I have created something not too bad since it is working as expected, but I doubt that what I’m about to show with this article will be a perfect first attempt.
Because of that, I encourage you to try to get the most out of this article, even if it isn’t perfect and nobody with fundamental Django skills would consider it to be a great start into Django, most certainly. But do not take anything in this article blindly as the one and only truth.

If you are more familiar with Django or do have better ideas with any of the shown codes or concepts, please let me know in the comments. I would really appreciate some feedback; positive or negative. I will adapt valuable advice by updating this article, accordingly.

Providing a proper environment

In this project, we will use the following:

  • Django
    • For our Telegram Bot, we will use Django 2.1.
  • Database
    • To not make the local setup too complicated, we will use a sqlite3 database for local development and testing.
    • For the production application in Heroku, we will use a PostgreSQL database, since this is a great SQL RDBMS and one of the three managed data services, which are available to all customers in Heroku.
      If you want to learn more about PostgreSQL in Heroku please read Herokus article on heroku-postgres.
  • Python
    • Since Heroku’s current default Python is 3.6.6 (as of 2018-08-24), I will also use 3.6; it’s not necessary to exactly match the patch level version.
    • If you want to use Python 3.7 for any reason (utilization of Data Classes for example), you can do so, but you will need an additional step, not explicitly mentioned later on anymore:
      To define Python 3.7 as the desired Python runtime in your project, add 
      python-3.7.0
       to a file called 
      runtime.txt
       inside your project folder (
      echo 'python-3.7.0' > runtime.txt
      ). Heroku will automatically pick that up and deploy your dynos with this runtime. More on Python runtimes on Heroku in python-runtimes article.

To match this environment, it is recommended to create a new virtualenv for it. I will show how to do this using pipenv in a minute.

Preparing your project

In the previous part of this series (which was optional), we created a new app on Heroku and initialized a simple Flask application in 

~/tbot
. We do not need any of that in the upcoming parts since it was meant to demonstrate Heroku’s workflow, exclusively. If you followed that article, you may leave everything in place like it is. If it isn’t used (no one is accessing its URL), it won’t consume any resources.

For this article, we will initiate a new project folder at 

~/dtbot
. If you prefer another location, feel free to do so, but please adopt that in the following occurrences of 
~/dtbot
  accordingly.

First, we need to have this empty folder created, initialize Git in it and create a fresh virtualenv using

pipenv
 :

~ $ mkdir ~/dtbot
~ $ cd ~/dtbot

~/dtbot $ git init
Initialized empty Git repository in /home/testuser/dtbot/.git/
~/dtbot $ echo 'db.sqlite3' > .gitignore
~/dtbot $ echo '**/__pycache__/' >> .gitignore
~/dtbot $ echo '*.pyc' >> .gitignore

~/dtbot $ pipenv --python python3.6.6
Creating a virtualenv for this project…
Using /opt/python/python3.6.6/bin/python3.6.6 (3.6.6) to create virtualenv…
⠋Running virtualenv with interpreter /opt/python/python3.6.6/bin/python3.6.6
Using base prefix '/opt/python/python3.6.6'
New python executable in /home/testuser/.virtualenvs/dtbot-hT9CNosh/bin/python3.6.6
Also creating executable in /home/testuser/.virtualenvs/dtbot-hT9CNosh/bin/python
Installing setuptools, pkg_resources, pip, wheel...done.

Virtualenv location: /home/testuser/.virtualenvs/dtbot-hT9CNosh
Creating a Pipfile for this project…
~/dtbot $

Next, we will install some required Python modules:

~/dtbot $ pipenv install django telepot gunicorn
Installing django…
Collecting django
  Using cached https://files.pythonhosted.org/packages/51/1a/e0ac7886c7123a03814178d7517dc822af0fe51a72e1a6bff26153103322/Django-2.1-py3-none-any.whl

...

Updated Pipfile.lock (dcba73)!
Installing dependencies from Pipfile.lock (dcba73)…
  🐍▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉ 13/13 — 00:00:03
To activate this project's virtualenv, run the following:
 $ pipenv shell
~/dtbot $

For those of you who are using Heroku to host your Django bot, install an additional module called

django-heroku
:

~/dtbot $ pipenv install django-heroku
Installing django-heroku…
...

django-heroku
 automatically pulls in some additional modules:
  • dj-database-url
  • psycopg2
  • whitenoise

With these modules installed, we can simplify and optimize the Django integration with Heroku; please read the project page for details. I will explain how to utilize these shortly.

Create a new Heroku app

While you are inside your local project folder (with the freshly initialized Git repo in it), create a new app in your Heroku environment (for details on this process, please read the previous part of this series):

(dtbot-hT9CNosh) ~/dtbot $ heroku create
Creating app... done, ⬢ dry-tundra-61874
https://dry-tundra-61874.herokuapp.com/ | https://git.heroku.com/dry-tundra-61874.git
(dtbot-hT9CNosh) ~/dtbot $ git remote -v
heroku  https://git.heroku.com/dry-tundra-61874.git (fetch)
heroku  https://git.heroku.com/dry-tundra-61874.git (push)
(dtbot-hT9CNosh) ~/dtbot $

We will need this, soon.

Activate your virtualenv and initiate your project

We can activate our virtualenv now. I recommend to do it like this:

~/dtbot $ source $(pipenv --venv)/bin/activate
(dtbot-hT9CNosh) ~/dtbot $

This last command is a bit special in my personal workflow: I do not like the pipenv default way to spawn a sub-shell using 

pipenv shell
. So I’m activating the virtualenv pipenv creates by sourcing it’s “activate”-script the classical virtualenv-way.

Now, the time has come to initiate your Django project:

(dtbot-hT9CNosh) ~/dtbot $ django-admin startproject dtbot .
(dtbot-hT9CNosh) ~/dtbot $ ls -l
total 28
-rw-rw-r-- 1 testuser testuser   241 Aug 24 15:58 Pipfile
-rw-rw-r-- 1 testuser testuser 13515 Aug 24 15:59 Pipfile.lock
drwxrwxr-x 2 testuser testuser  4096 Aug 24 16:15 dtbot
-rwxrwxr-x 1 testuser testuser   537 Aug 24 16:15 manage.py
(dtbot-hT9CNosh) ~/dtbot $ ls dtbot/
__init__.py  settings.py  urls.py  wsgi.py
(dtbot-hT9CNosh) ~/dtbot $

Let’s calibrate some settings in 

~/dtbot/dtbot/settings.py
 to match Heroku’s recommended implementation first:
  • Add 
    import django_heroku
     to the top of your 
    settings.py
     file.
    Then, to the very bottom of it, add
    django_heroku.settings(locals())
    . By this, some sensitive credentials (like the database credentials) and secrets (like
    SECRET_KEY
    ) and general settings (like 
    DATABASE_URL
    ) do not need to be stored in the 
    settings.py
     file, but are configured using environment variables inside of the dyno or work out-of-the-box because Heroku provides these values in environment variables automatically.
    This adds some value to your project since you will need to configure less in files. Also, not even your co-workers will get these secrets exposed, your app will become re-usable without the need to make any change to its 
    settings.py
     file, et cetera. Even if there are no co-workers, maybe there will be some in the future or you decide to make your bot’s code publically available on Github or similar; you can’t know today what will happen in the future. Because of that, adding sensitive information to a VCS like Git is not recommended generally.
    To learn about the details of this, please read Herokus django-app-configuration article.
  • Add 
    import os
     to the top of 
    settings.py
    . Then replace 
    DEBUG = True
     by this:
    DEBUG = os.environ.get('DEBUG')
    if DEBUG is None:
        DEBUG = False
    else:
        if 'True' in DEBUG:
            DEBUG = True
        else:
            DEBUG = False

    I will explain the logic behind this in a minute.
  • Add 
    127.0.0.1
     to 
    ALLOWED_HOSTS
     , so you can access your Django locally.

As a last initialization step, apply any migrations now; these will be applied to the default sqlite3 DB file 

db.sqlite3
 in your project folder:

(dtbot-hT9CNosh) ~/dtbot $ python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, sessions
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying admin.0003_logentry_add_action_flag_choices... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying auth.0009_alter_user_last_name_max_length... OK
  Applying sessions.0001_initial... OK
(dtbot-hT9CNosh) ~/dtbot $ ls -l db.sqlite3
-rw-r--r-- 1 testuser testuser 40960 Aug 24 16:59 db.sqlite3
(dtbot-hT9CNosh) ~/dtbot $

That’s it so far – let’s test this and play around with it a bit next.

Start the build-in HTTP server and enable DEBUG

Let’s test drive our setup so far. Run your app now like this:

(dtbot-hT9CNosh) ~/dtbot $ python manage.py runserver
Performing system checks...

System check identified no issues (0 silenced).
August 24, 2018 - 15:02:58
Django version 2.1, using settings 'dtbot.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

When you point your browser to 

http://127.0.0.1:8000/
, you should notice two things:
  1. This should work and a web page should be shown.
  2. The content of the web page should be:
    Not_Found
    If you ever had worked with Django before, you may have expected a DEBUG – page with additional details. This is not shown since we changed 
    DEBUG = True
     in the 
    settings.py
     file for our environment extraction logic before. By default (when there is no 
    DEBUG
     variable with a value of 
    True
     assigned to it) this results in 
    False
     as a security measure.
    To have the DEBUG – page shown here instead set the environment variable
    DEBUG
    to
    True
     before starting the HTTP server using 
    python manage.py runserver
     like this:
    (dtbot-hT9CNosh) ~/dtbot $ export DEBUG="True"
    (dtbot-hT9CNosh) ~/dtbot $ python manage.py runserver
    Performing system checks...
    
    System check identified no issues (0 silenced).
    August 24, 2018 - 15:12:09
    Django version 2.1, using settings 'dtbot.settings'
    Starting development server at http://127.0.0.1:8000/
    Quit the server with CONTROL-C.

    Now, when you refresh your browser, the DEBUG page should be shown instead:

django_21_debug_page

Heroku config vars

So – why did we change the way 

DEBUG
 is switched now?
In containerized environments, settings are often defined using environment variables inside the containers, since this enables the users to use reusable applications without changing any files in each container. It’s just more flexible and easier to orchestrate than config file settings, since scaling and multi-node setups are much more a standard pattern in containerization than it is in the bare metal world. Heroku is no exception to this.

You can change and list environment variables for an application in Heroku using the 

heroku
 CLI. To show all registered config vars and their value, just execute 
heroku config
. New ones can be set or existing ones can be changed using 
heroku config:set
:

(dtbot-hT9CNosh) ~/dtbot $ heroku config
=== dry-tundra-61874 Config Vars

(dtbot-hT9CNosh) ~/dtbot $ heroku config:set DEBUG=True
Setting DEBUG and restarting ⬢ dry-tundra-61874... done, v3
DEBUG: True
(dtbot-hT9CNosh) ~/dtbot $ heroku config
=== dry-tundra-61874 Config Vars
DEBUG: True
(dtbot-hT9CNosh) ~/dtbot $

Now, whenever you push a change to your project, it will be deployed with Debugging enabled.
🚨 Most certainly, you do not want that for a productive application 🚨! There are more advanced methods to debug a remote Django web application than to expose it’s debugging data to the world!
But for this tutorial-like application, it is a great and easy example to demonstrate how this concept is working on Heroku.
Leave this set to 

True
; we will push our Django app to Heroku soon. Then, we will play around with this a bit and finally switch the content of the 
DEBUG
 config var to 
False
, before we will never touch it again 😉

Prepare our Django app to be shipped 🎁

Before we can deploy our app on Heroku, we need to do two more preparations:

Add a PostgreSQL RDBMS to your Heroku app 🐘

In Heroku, this is only one command:

(dtbot-hT9CNosh) ~/dtbot $ heroku addons:add heroku-postgresql
Creating heroku-postgresql on ⬢ dry-tundra-61874... free
Database has been created and is available
 ! This database is empty. If upgrading, you can transfer
 ! data from another database with pg:copy
Created postgresql-rectangular-59399 as DATABASE_URL
Use heroku addons:docs heroku-postgresql to view documentation
(dtbot-hT9CNosh) ~/dtbot $

That’s it!
When you check your app’s config vars, you will notice that a new variable called 

DATABASE_URL
 was added to your application. Also, it now shows up in the list of addons:

(dtbot-hT9CNosh) ~/dtbot $ heroku addons

Add-on                                            Plan       Price  State  
────────────────────────────────────────────────  ─────────  ─────  ───────
heroku-postgresql (postgresql-rectangular-59399)  hobby-dev  free   created
 └─ as DATABASE

The table above shows add-ons and the attachments to the current app (dry-tundra-61874) or other apps.
  
(dtbot-hT9CNosh) ~/dtbot $ heroku config
=== dry-tundra-61874 Config Vars
DATABASE_URL: postgres://${PG_USER}:${PG_PASS}@ec2-54-83-51-78.compute-1.amazonaws.com:5432/${PG_DBNAME}
DEBUG:        True
(dtbot-hT9CNosh) ~/dtbot $

This way, your Django app is connected to your PostgreSQL database automatically, since the connection string defined in 

DATABASE_URL
 is picked up by the 
django_heroku
 module we added to our 
settings.py
 file earlier.

This way, you are done preparing a PostgreSQL database for your Heroku hosted web app with just one single command 👍
Isn’t this just awesome?
But there is even more: You can even connect to that DB by extracting the necessary parts for your application (pgAdminIII, psql, …) from that URL. If you want to use psql from your current workstation, there is even a more convenient way:

(dtbot-hT9CNosh) ~/dtbot $ heroku pg:psql
--> Connecting to postgresql-rectangular-59399
psql (10.5 (Ubuntu 10.5-1.pgdg16.04+1), server 10.4 (Ubuntu 10.4-2.pgdg14.04+1))
SSL connection (protocol: TLSv1.2, cipher: ECDHE-RSA-AES256-GCM-SHA384, bits: 256, compression: off)
Type "help" for help.

dry-tundra-61874::DATABASE=>

So, in theory, whenever you need a PostgreSQL DB for a project hosted literally anywhere, you could just create an empty Heroku app, add an heroku-postgres addon to it and start utilizing that DB.

To me, this is pure elegance!

Create a Procfile

Finally, as the last preparation step, let’s get one more detail out of the way: Create a 

Procfile
 to configure how Heroku should startup your dyno with but one line as content:

web: gunicorn dtbot.wsgi

If you want to know more about a 

Procfile
, please read the previous part of this series or the corresponding Heroku article on the Procfile.

Ship your app! 🛳

We now should have the basics set and be ready to commit our files to the Git repository and to push it to the heroku remote to trigger a deployment.

🚨 Once more a warning 🚨: Remember that since we still have the config var 

DEBUG
 set to 
True
, our app will be in debugging mode! We do not really have valuable data in it yet and it is also more than unlikely that in the few minutes between our deployment and the moment when we switch it to 
False
 someone with bad intention will visit our unpublished, randomly generated URL. But please decide this on your own. If you do not feel comfortable with this and do not need to see the debug page on your own environment to get an idea for the environment, better switch config var 
DEBUG
 to 
False
 before you do your push to heroku master.

Deploy your prepared Django app like this:

(dtbot-hT9CNosh) ~/dtbot $ git add .
(dtbot-hT9CNosh) ~/dtbot $ git commit -m "Init"
[master (root-commit) 3b3927c] Init
 13 files changed, 440 insertions(+)
 create mode 100644 .gitignore
 create mode 100644 Pipfile
 create mode 100644 Pipfile.lock
 create mode 100644 Procfile
 create mode 100644 dtbot/__init__.py
 create mode 100644 dtbot/settings.py
 create mode 100644 dtbot/urls.py
 create mode 100644 dtbot/wsgi.py
 create mode 100755 manage.py
(dtbot-hT9CNosh) ~/dtbot $ git push heroku master
Counting objects: 24, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (19/19), done.
Writing objects: 100% (24/24), 10.99 KiB | 0 bytes/s, done.
Total 24 (delta 3), reused 0 (delta 0)
remote: Compressing source files... done.
remote: Building source:
remote: 
remote: -----> Python app detected
remote: -----> Installing python-3.6.6
remote: -----> Installing pip
remote: -----> Installing dependencies with Pipenv 2018.5.18…
remote:        Installing dependencies from Pipfile.lock (ce9952)…
remote: -----> Installing SQLite3
remote: -----> $ python manage.py collectstatic --noinput
remote:        /app/.heroku/python/lib/python3.6/site-packages/psycopg2/__init__.py:144: UserWarning: The psycopg2 wheel package will be renamed from release 2.8; in order to keep installing from binary please use "pip install psycopg2-binary" instead. For details see: <http://initd.org/psycopg/docs/install.html#binary-install-from-pypi>.
remote:          """)
remote:        119 static files copied to '/tmp/build_eb69259fca4b3ef8687a35ccdf98d226/staticfiles', 375 post-processed.
remote: 
remote: -----> Discovering process types
remote:        Procfile declares types -> web
remote: 
remote: -----> Compressing...
remote:        Done: 64.8M
remote: -----> Launching...
remote:        Released v5
remote:        https://dry-tundra-61874.herokuapp.com/ deployed to Heroku
remote: 
remote: Verifying deploy... done.
To https://git.heroku.com/dry-tundra-61874.git
 * [new branch]      master -> master
(dtbot-hT9CNosh) ~/dtbot $

“remote: … deployed to Heroku” – exactly what we were hoping for to see! When we navigate to our apps URL (

https://dry-tundra-61874.herokuapp.com/
 in this example), we should now see the DEBUG page we did before when we tested locally. For convenience, you can simply execute
heroku open
 to direct your browser to the correct URL:

heroku_django_debug_page
… let’s disable the debugging now to make that bad tummy feeling go away before we proceed.
In your shell, simply execute

heroku config:set DEBUG=False
 :

(dtbot-hT9CNosh) ~/dtbot $ heroku config:set DEBUG=False
Setting DEBUG and restarting ⬢ dry-tundra-61874... done, v6
DEBUG: False
(dtbot-hT9CNosh) ~/dtbot $

With immediate effect, the debugging mode should be disabled when you refresh your browser:

django_on_heroku_without_debug

Phew, that happened to become quite a long article, hasn’t it?
Because of this, I will stop now and better continue in the next part.

So far, you managed to create your Django project skeleton, prepare it for Heroku and deployed it already! That is enough for one day anyway, I guess.

Outlook for the next part of the series

We just learned how to prepare a proper local environment for Django and Heroku by creating a virtualenv using pipenv. Also, we saw how to kickstart a new Django project, make changes to its config appropriate for Heroku hosting, deploy it to Heroku and how to use Herokus config vars properly to quickly change settings in your running and future deployed apps. Finally, we already had a peek into some of Herokus convenient development features and how to utilize them by the heroku CLI.

In the next article of this series, we will see some more of Herokus features which support you in your development. We also need to do some other actions to finalize our Django app in Heroku; we neither have applied our migrations to the PostgreSQL DB, nor created a superuser to access the Admin Area yet. Finally, we will create a Django app which can receive the JSON data sent by your Telegram bot and do something with it.

If you liked or disliked this article, I’d love to read that in the comments!

Enjoy coding!

Born in 1982, Marc Richter is an IT enthusiastic since 1994. He became addicted when he first put hands on their family’s pc and never stopped investigating and exploring new things since then.
He is married to Jennifer Richter and proud father of two wonderful children, Lotta and Linus.
His current professional focus is DevOps and Python development.

An exhaustive bio can be found at this blog post.

Found my articles useful? Maybe you would like to support my efforts and give me a tip then?

Martin Fitzpatrick: Raindar — Desktop daily weather, forecast app in PyQt

$
0
0

The Raindar UI was created using Qt Designer, and saved as .ui file, which is available for download. This was converted to an importable Python file using pyuic5.

API key

Before running the application you need to obtain a API key from OpenWeatherMap.org. This key is unique to you, and should be kept secret (don't add it to your code). To use a key with Raindar, you need to make it available in the environment variable OPENWEATHERMAP_API_KEY.

On Linux or Mac you can do this by using export in the shell.

exportOPENWEATHERMAP_API_KEY=<yourkeyhere>

The key is then obtained from the environment by the following line in our code. The variable OPENWEATHERMAP_API_KEY will now contain your API key can be be used for requests.

OPENWEATHERMAP_API_KEY=os.environ.get('OPENWEATHERMAP_API_KEY')"""Get an API key from https://openweathermap.org/ to use with thisapplication."""

Requesting data

Requests to the API can take a few moments to complete. If we perform these in the main application loop this will cause our app to hang while waiting for data. To avoid this we perform all requests in seperate worker threads,

This worker collects both the current weather and a forecast, and returns this to the main thread to update the UI.

First we define a number of custom signals which the worker can emit. These include finished a generic signal for the worker completing, error which emits an Exception message should an error occur and result which returns the result of the API call. The data is returned as two separate dict objects, one representing the current weather and one for the forecast.

classWorkerSignals(QObject):'''    Defines the signals available from a running worker thread.    '''finished=pyqtSignal()error=pyqtSignal(str)result=pyqtSignal(dict,dict)

The WeatherWorker runnable handles the actual requests to the API. It is initialized with a single parameter location which gives the location that the worker will retrieve the weather data for. Each worker performs two requests, one for the weather, and one for the forecast, receiving a JSON strings from the OpenWeatherMap.org. These are then unpacked into dict objects and emitted using the .result signal.

classWeatherWorker(QRunnable):'''    Worker thread for weather updates.    '''signals=WorkerSignals()is_interrupted=Falsedef__init__(self,location):super(WeatherWorker,self).__init__()self.location=location@pyqtSlot()defrun(self):try:params=dict(q=self.location,appid=OPENWEATHERMAP_API_KEY)url='http://api.openweathermap.org/data/2.5/weather?%s&units=metric'%urlencode(params)r=requests.get(url)weather=json.loads(r.text)# Check if we had a failure (the forecast will fail in the same way).ifweather['cod']!=200:raiseException(weather['message'])url='http://api.openweathermap.org/data/2.5/forecast?%s&units=metric'%urlencode(params)r=requests.get(url)forecast=json.loads(r.text)self.signals.result.emit(weather,forecast)exceptExceptionase:self.signals.error.emit(str(e))self.signals.finished.emit()

The MainWindow

The main window layout is defined in Qt Designer. To create the mainwindow we simply create a subclass of Ui_MainWindow (and QMainWindow) and call self.setupUi(self) as normal.

To trigger the request for weather data using the push button we connect it's pressed signal to our custom update_weather slot.

Finally we create our thread pool class, to handle running our workers and show the main window.

classMainWindow(QMainWindow,Ui_MainWindow):def__init__(self,*args,**kwargs):super(MainWindow,self).__init__(*args,**kwargs)self.setupUi(self)self.pushButton.pressed.connect(self.update_weather)self.threadpool=QThreadPool()self.show()

Refresh data

Pressing the button triggers the update_weather slot method. This creates a new WeatherWorker instance, passing in the currently set location from the lineEdit box. The result and error signals of the worker are connected up to the weather_result handler, and to our custom alert handler respectively.

The alert handler uses QMessageBox to display a message box window, containing the error from the worker.

defupdate_weather(self):worker=WeatherWorker(self.lineEdit.text())worker.signals.result.connect(self.weather_result)worker.signals.error.connect(self.alert)self.threadpool.start(worker)defalert(self,message):alert=QMessageBox.warning(self,"Warning",message)

Handling the result

The weather and forecastdict objects returned by the workers are emitted through the result signal. This signal is connected to our custom slot weather_result, which receives the two dict objects. This method is responsible for updating the UI with the result returned, showing both the numeric data and updating the weather icons.

The weather results are updated to the UI by setText on the defined QLabels, formatted to decimal places where appropriate.

defweather_result(self,weather,forecasts):self.latitudeLabel.setText("%.2f°"%weather['coord']['lat'])self.longitudeLabel.setText("%.2f°"%weather['coord']['lon'])self.windLabel.setText("%.2f m/s"%weather['wind']['speed'])self.temperatureLabel.setText("%.1f°C"%weather['main']['temp'])self.pressureLabel.setText("%d"%weather['main']['pressure'])self.humidityLabel.setText("%d"%weather['main']['humidity'])self.weatherLabel.setText("%s (%s)"%(weather['weather'][0]['main'],weather['weather'][0]['description'])

The timestamps are processed using a custom from_ts_to_time_of_day function to return a user-friendlier time of day in am/pm format with no leading zero.

deffrom_ts_to_time_of_day(ts):dt=datetime.fromtimestamp(ts)returndt.strftime("%I%p").lstrip("0")self.sunriseLabel.setText(from_ts_to_time_of_day(weather['sys']['sunrise']))

The OpenWeatherMap.org has a custom mapping for icons, with each weather state indicated by a specific number — the full mapping is available here. We're using the free fugue icon set, which has a pretty complete set of weather-related icons. To simplify the mapping between the OpenWeatherMap.org and the icon set, the icons have been renamed to their respective OpenWeatherMap.org numeric code.

defset_weather_icon(self,label,weather):label.setPixmap(QPixmap(os.path.join('images',"%s.png"%weather[0]['icon'])))

First we set the current weather icon, from the weather dict, then iterate over the first 5 of the provided forecasts. The forecast icons, times and temperature labels were defined in Qt Designer with the names forecastIcon<n>, forecastTime<n> and forecastTemp<n>, making it simple to iterate over them in turn and retrieve them using getattr with the current iteration index.

self.set_weather_icon(self.weatherIcon,weather['weather'])forn,forecastinenumerate(forecasts['list'][:5],1):getattr(self,'forecastTime%d'%n).setText(from_ts_to_time_of_day(forecast['dt']))self.set_weather_icon(getattr(self,'forecastIcon%d'%n),forecast['weather'])getattr(self,'forecastTemp%d'%n).setText("%.1f°C"%forecast['main']['temp'])

The full source is available on Github.

Improvements

A few simple ways you could extend this application —

  1. Eliminate repeated requests for the data, by using request_cache. This will persist the request data between runs.
  2. Support for multiple locations.
  3. Configurable forecast length.
  4. Make the current weather available on a toolbar icon while running.

Vasudev Ram: Automatically enter interactive mode after Python exception

$
0
0
By Vasudev Ram



Bug image attribution

Hi, readers,

Here's a Python command-line option that can facilitate debugging, when your program raises an exception:

It's the Python interpreter's -i option.

Here is a small Python program (ent-inter.py, for enter interactive mode) to show how the option can be used:
$ type ent-inter.py

from __future__ import print_function

print("before for loop")
for divisor in (1, 0, -1):
print("1/{} = {}".format(divisor, 1/divisor))
print("after for loop")
If I run it in the normal way (without the -i option), I get:
$ python ent-inter.py
before for loop
1/1 = 1
Traceback (most recent call last):
File "ent-inter.py", line 6, in
print("1/{} = {}".format(divisor, 1/divisor))
ZeroDivisionError: integer division or modulo by zero
The error message does tell us that there was a division by zero. But it doesn't tell us what exactly caused it.

Okay, in this particular case, from the stack trace (in particular, from the print statement), we can figure out that the variable divisor must have been zero. That was because I kept the code small and simple, though, for illustrative purposes.

But suppose that the cause of the error had been a more complex arithmetic expression, say with multiple division operations, or some other kind of statement (or sequence of statements). In that case, just the stack trace alone might not be enough for us to figure out the root cause of the error.

If we could immediately be launched into a Python interactive session with the current state (i.e. the variables) of the crashed program still available to inspect, that would likely help us to find the root cause. This is what the -i option helps with.

Let's see how:
$ python -i ent-inter.py
before for loop
1/1 = 1
Traceback (most recent call last):
File "ent-inter.py", line 6, in
print("1/{} = {}".format(divisor, 1/divisor))
ZeroDivisionError: integer division or modulo by zero
>>> divisor
0
>>>
I ran the same program again, but this time with the -i option given.

After the program crashed due to the ZeroDivisionError, an interactive Python shell was automatically launched (as we can see from the Python prompt shown).

I typed "divisor" at the prompt, and the shell printed that variable's current value, 0.
From this (plus the stack trace), we can see that this value is the cause of the error. Then we can look one line above, in the program's code (at the for statement) and see that one of the items in the tuple is a zero.

Of course, we can run the program under the control of a command-line debugger (like pdb) or single-step through the code in an IDE, to find the error. But that will only work if the error occurs during one of those runs. If the error is intermittent, running the program multiple times using the debugger or an IDE, will be tedious and time-consuming.

Instead, with this -i option, we can run the program as many times as we want, and for the times when it works properly, we don't waste any time stepping through the code. But when it does give an error like the above one, we are launched into the interactive mode, with the state of the crashed program available for inspection, to help debug the issue.

Another advantage of this approach is that we may not need to replicate the problem, because we have it right there in front of us (due to use of the -i option), and also, replicating the exact conditions that cause a bug is not always easy.

Note: I ran this program with the -i option on both Python 2.7 and Python 3.7 [1] on Windows, and on Python 2.7 on Linux. Got the same results in all 3 cases, except that in Python 3, the error message is slightly different:

ZeroDivisionError: division by zero

[1] Running it with Python 3 (on Windows) can be done in the usual way, by changing your PATH to point to Python 3 (if it is not already set to that), or by using Py, the Python launcher for Windows, like this:
$ py -3 -i ent-inter.py
So, overall, Python's -i option is useful.

Here is an excerpt from the output of "python -h" (the python command's help option):
-i     : inspect interactively after running script;
The picture at the top of the post is of one of the first software bugs recorded.

Read that story here on Wikipedia: Software bug

Enjoy.

Interested in a standard or customized Python course? Contact me

- Vasudev Ram - Online Python training and consulting

Hit the ground running with my vi quickstart tutorial.

Jump to posts: Python * DLang * xtopdf

Subscribe to my blog by email

My ActiveState Code recipes

Follow me on: LinkedIn * Twitter

Are you a blogger with some traffic? Get Convertkit:

Email marketing for professional bloggers



Martin Fitzpatrick: Driving I2C OLED displays with MicroPython — I2C monochrome displays with SSD1306

$
0
0

The following instructions are based on a 0.91in monochrome OLED, but other displays using the same chipset can be used with this library.

Requirements
Wemos D1 v2.2+ or good imitations.Buy 0.91in OLED Screen 128x32 pixels, I2c interface.Buy Breadboard Any size will do.Buy Wires Loose ends, or jumper leads.

Setting up

There is a Python ssd1306 module for OLED displays available in the MicroPython repository. Click Raw format and save the file with a .py extension.

You can then use the ampy tool (or the WebREPL) to upload the file to your device's filesystem:

ampy --port /dev/tty./dev/tty.wchusbserial141120 put ssd1306.py

With the ssd1306.py file on your Wemos D1, you can import it as any other Python module. Connect to your device, and then in the REPL enter:

frommachineimportI2C,Pinimportssd1306

If the import ssd1306 succeeds, the package is correctly uploaded and you're good to go.

Wire up the OLED display, connecting pins D1 to SCL and D2 to SDA. Provide power from G and 5V.

The circuit

Using the interface

To use the display you first need to create an I2C interface. In MicroPython I2C is via a software implementation so you can put it on any GPIO pins you like.

i2c=I2C(-1,Pin(5),Pin(4))display=ssd1306.SSD1306_I2C(128,32,i2c)

The SSD1306 module makes use of the MicroPython framebuf frame buffer, an efficient in-memory buffer for working with a simple graphics view. The methods for drawing text and primitives are from this framebuffer implementation. For a complete overview of what's available, check the MicroPython documentation.

The setup of the framebuffer format (monochrome, bit ordering, etc.) is also handled by the SSD1306 library. Check the framebuf documentation for more info on available options for other displays.

To test your I2C connection to the display, fill the display in solid colour.

display.fill(1)# Fill the entire display with 1="on"display.show()

You need to call display.show() to actually send the current framebuf to the device.

The display, filled

Drawing primitives

The .fill() method can be used to fill the entire display with a specified colour. Note that this changes all pixels to the given colour, and does not perform a flood fill of matching regions.

# Fill the entire display with colour 0display.fill(0)

The display, filled with blank

For filling specific regions of the display see .fill_rect() below.

Setting individual pixels can be accomplished using .pixel(). This is only a good idea when you area setting relatively few pixels as it is much slower than using other methods.

# Set the pixel at 3, 4 (x, y) to 1# .pixel(x, y, c)display.pixel(3,4,1)# 3rd param is the colourdisplay.show()

A single pixel

If you don't provide a color via parameter c, this method returns the colour of the pixel at the specified coordinates.

# Return the value at 3, 4 (x, y)# .pixel(x, y)c=display.pixel(3,4)

This is not a particularly nice API. In other cases (e.g. text) omitting the optional c will use 1 as a default.

Horizontal and vertical lines can be drawn with .hline() and .vline() respectively, providing a starting x,y location and line length and colour.

# Draw a horizontal line, starting from 2, 3 (x, y), 4 pixels wide # .hline(x, y, w, c)display.hline(2,3,25,1)display.show()

A horizontal line

# Draw a vertical line, starting from 5, 0 (x, y), 6 pixels high# .vline(x, y, h, c)display.vline(5,0,15,1)display.show()

A vertical line

For diagonal lines, the .line() method can be used to draw lines between between two sets of points x1,y1 and x2,y2 specified in order. The parameter c controls the colour of the line drawn.

# Draw a short diagonal line down to the right.# .line(x1, y1, x2, y2, c)display.line(0,0,50,25,1)display.show()

A diagonal line

There is no antialiasing, so diagonal lines will probably look pretty jaggy.

The .draw_rect() method allows you to draw a unfilled rectangle, starting at x,y and with a specified width w and height h. The specified colour c is used to draw the boundary of the rectangle, but it is not filled.

# Draw an unfilled rectangle of 8, 5 pixels, starting at 1,1 in colour 1.# .rect(x, y, w, h, c)display.rect(5,5,100,20,1)display.show()

An empty rectangle

You can also draw filled rectangles, using .fill_rect(). The parameters are the same as for .rect() but all pixels within the boundary will be set.

# Draw a filled rectangle of 10x5 pixels, starting at 3,3 in colour 1# .fill_rect(x, y, w, h, c)display.fill_rect(9,9,25,25,1)display.show()

An filled rectangle

Writing text

The framebuffer class provides support for writing text using a simple 8x8 bitmap font. The pixel x,y positions are relative to the top-left of the 8x8 character, so positioning at 0,0 will give the absolute top left of the screen.

# Print "Hello world!" 1 pixel from top left, in colour 1 (on)# .text(text, x, y, c)display.text("Hello world!",1,1,1)display.show()

Hello world

The .text() method takes an optional 4th parameter color, which gives the colour to draw text in. On a mono screen this can be either 0 (off) or 1 (on).

# Print "Hello world!" at 2,2 in colour 0 (off)display.text("Hello world!",2,2,0)display.show()

Hello world, black over

# Print "Hello world!" at 3,3 in colour 1 (on)display.text("Hello world!", 3, 3, 1)
display.show()

Hello world, white over black

Scrolling

You can shift the contents of the entire framebuffer around using .scroll, which takes x and y parameters to specify the scroll (positive/negative) in each dimension.

# .scroll(x, y)display.scroll(-10,0)

Scrolled left

There is no support for scrolling a portion of the framebuffer. You can however keep a separate framebuffer which you scroll and then blit to your main display — see the next section.

Pixel graphics

For very simple graphics that do not update often you can get away with writing bitmap graphics to the framebuffer pixel by pixel. For example, in the following code block we draw an icon from a list-of-lists of binary colour data, iterating over with simple loops:

ICON=[[0,0,0,0,0,0,0,0,0],[0,1,1,0,0,0,1,1,0],[1,1,1,1,0,1,1,1,1],[1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1],[0,1,1,1,1,1,1,1,0],[0,0,1,1,1,1,1,0,0],[0,0,0,1,1,1,0,0,0],[0,0,0,0,1,0,0,0,0],]display.fill(0)# Clear the displayfory,rowinenumerate(ICON):forx,cinenumerate(row):display.pixel(x,y,c)display.show()

Bitmap heart graphic

Using urandom we can scatter images over the display, like in the following example:

importurandomdefrandom_heart():xofs=urandom.getrandbits(8)yofs=urandom.getrandbits(5)fory,rowinenumerate(ICON):forx,cinenumerate(row):display.pixel(x+xofs,y+yofs,c)forninrange(100):random_heart()display.show()

Scattered heart graphics

If you can add an if c around the display.pixel call to only output pixels which are on — effectively masking, and avoiding the black square.

For bigger graphics or where you need faster updates, you can instead blit your bitmap images from one framebuffer to another. Pass the framebuf to blit, and the coordinates x and y to blit at.

# Blit a framebuffer at the pixel position 1, 1.# display.blit(fbuf, x, y)display.blit(fbuf,1,1)

The blit method takes an optional 4th parameter key which is a colour to considered 'transparent' when blitting. Pixels with this value won't be copied over onto the target display.

# Blit except pixels with value 0 (i.e. only pixels with value 1)# .blit(fbuf, x, y, key)display.blit(fbuf,1,1,key=0)

Display control

The drawing methods so far are inherited from the the MicroPython framebuf object, which is written to the display on .update(). But the ssd1306 object itself also provides methods for direct control of the display component.

The display can be turned on and off using display.poweron() and display.poweroff() respectively.

You can set the contrast for the display using .contrast() passing in parameter c which is a value between 0 and 255. This controls the contrast between the foreground, active colour 1 and the background 0.

# Set the display contrast to half (127/255 = 0.5)# .contrast(c)display.contrast(50)

Contrast 50

To invert the display, switching foreground and background colours, call .invert(1).

display.invert(1)

Inverted

Calling .invert(0) will return the display to how it was originally. You can use this for display-flashing visual effects:

importtimewhileTrue:display.invert(0)time.sleep(0.01)display.invert(1)time.sleep(0.01)

REPL|REBL: Raindar — Desktop daily weather, forecast app in PyQt

$
0
0

The Raindar UI was created using Qt Designer, and saved as .ui file, which is available for download. This was converted to an importable Python file using pyuic5.

API key

Before running the application you need to obtain a API key from OpenWeatherMap.org. This key is unique to you, and should be kept secret (don't add it to your code). To use a key with Raindar, you need to make it available in the environment variable OPENWEATHERMAP_API_KEY.

On Linux or Mac you can do this by using export in the shell.

exportOPENWEATHERMAP_API_KEY=<yourkeyhere>

The key is then obtained from the environment by the following line in our code. The variable OPENWEATHERMAP_API_KEY will now contain your API key can be be used for requests.

OPENWEATHERMAP_API_KEY=os.environ.get('OPENWEATHERMAP_API_KEY')"""Get an API key from https://openweathermap.org/ to use with thisapplication."""

Requesting data

Requests to the API can take a few moments to complete. If we perform these in the main application loop this will cause our app to hang while waiting for data. To avoid this we perform all requests in seperate worker threads,

This worker collects both the current weather and a forecast, and returns this to the main thread to update the UI.

First we define a number of custom signals which the worker can emit. These include finished a generic signal for the worker completing, error which emits an Exception message should an error occur and result which returns the result of the API call. The data is returned as two separate dict objects, one representing the current weather and one for the forecast.

classWorkerSignals(QObject):'''    Defines the signals available from a running worker thread.    '''finished=pyqtSignal()error=pyqtSignal(str)result=pyqtSignal(dict,dict)

The WeatherWorker runnable handles the actual requests to the API. It is initialized with a single parameter location which gives the location that the worker will retrieve the weather data for. Each worker performs two requests, one for the weather, and one for the forecast, receiving a JSON strings from the OpenWeatherMap.org. These are then unpacked into dict objects and emitted using the .result signal.

classWeatherWorker(QRunnable):'''    Worker thread for weather updates.    '''signals=WorkerSignals()is_interrupted=Falsedef__init__(self,location):super(WeatherWorker,self).__init__()self.location=location@pyqtSlot()defrun(self):try:params=dict(q=self.location,appid=OPENWEATHERMAP_API_KEY)url='http://api.openweathermap.org/data/2.5/weather?%s&units=metric'%urlencode(params)r=requests.get(url)weather=json.loads(r.text)# Check if we had a failure (the forecast will fail in the same way).ifweather['cod']!=200:raiseException(weather['message'])url='http://api.openweathermap.org/data/2.5/forecast?%s&units=metric'%urlencode(params)r=requests.get(url)forecast=json.loads(r.text)self.signals.result.emit(weather,forecast)exceptExceptionase:self.signals.error.emit(str(e))self.signals.finished.emit()

The MainWindow

The main window layout is defined in Qt Designer. To create the mainwindow we simply create a subclass of Ui_MainWindow (and QMainWindow) and call self.setupUi(self) as normal.

To trigger the request for weather data using the push button we connect it's pressed signal to our custom update_weather slot.

Finally we create our thread pool class, to handle running our workers and show the main window.

classMainWindow(QMainWindow,Ui_MainWindow):def__init__(self,*args,**kwargs):super(MainWindow,self).__init__(*args,**kwargs)self.setupUi(self)self.pushButton.pressed.connect(self.update_weather)self.threadpool=QThreadPool()self.show()

Refresh data

Pressing the button triggers the update_weather slot method. This creates a new WeatherWorker instance, passing in the currently set location from the lineEdit box. The result and error signals of the worker are connected up to the weather_result handler, and to our custom alert handler respectively.

The alert handler uses QMessageBox to display a message box window, containing the error from the worker.

defupdate_weather(self):worker=WeatherWorker(self.lineEdit.text())worker.signals.result.connect(self.weather_result)worker.signals.error.connect(self.alert)self.threadpool.start(worker)defalert(self,message):alert=QMessageBox.warning(self,"Warning",message)

Handling the result

The weather and forecastdict objects returned by the workers are emitted through the result signal. This signal is connected to our custom slot weather_result, which receives the two dict objects. This method is responsible for updating the UI with the result returned, showing both the numeric data and updating the weather icons.

The weather results are updated to the UI by setText on the defined QLabels, formatted to decimal places where appropriate.

defweather_result(self,weather,forecasts):self.latitudeLabel.setText("%.2f°"%weather['coord']['lat'])self.longitudeLabel.setText("%.2f°"%weather['coord']['lon'])self.windLabel.setText("%.2f m/s"%weather['wind']['speed'])self.temperatureLabel.setText("%.1f°C"%weather['main']['temp'])self.pressureLabel.setText("%d"%weather['main']['pressure'])self.humidityLabel.setText("%d"%weather['main']['humidity'])self.weatherLabel.setText("%s (%s)"%(weather['weather'][0]['main'],weather['weather'][0]['description'])

The timestamps are processed using a custom from_ts_to_time_of_day function to return a user-friendlier time of day in am/pm format with no leading zero.

deffrom_ts_to_time_of_day(ts):dt=datetime.fromtimestamp(ts)returndt.strftime("%I%p").lstrip("0")self.sunriseLabel.setText(from_ts_to_time_of_day(weather['sys']['sunrise']))

The OpenWeatherMap.org has a custom mapping for icons, with each weather state indicated by a specific number — the full mapping is available here. We're using the free fugue icon set, which has a pretty complete set of weather-related icons. To simplify the mapping between the OpenWeatherMap.org and the icon set, the icons have been renamed to their respective OpenWeatherMap.org numeric code.

defset_weather_icon(self,label,weather):label.setPixmap(QPixmap(os.path.join('images',"%s.png"%weather[0]['icon'])))

First we set the current weather icon, from the weather dict, then iterate over the first 5 of the provided forecasts. The forecast icons, times and temperature labels were defined in Qt Designer with the names forecastIcon<n>, forecastTime<n> and forecastTemp<n>, making it simple to iterate over them in turn and retrieve them using getattr with the current iteration index.

self.set_weather_icon(self.weatherIcon,weather['weather'])forn,forecastinenumerate(forecasts['list'][:5],1):getattr(self,'forecastTime%d'%n).setText(from_ts_to_time_of_day(forecast['dt']))self.set_weather_icon(getattr(self,'forecastIcon%d'%n),forecast['weather'])getattr(self,'forecastTemp%d'%n).setText("%.1f°C"%forecast['main']['temp'])

The full source is available on Github.

Improvements

A few simple ways you could extend this application —

  1. Eliminate repeated requests for the data, by using request_cache. This will persist the request data between runs.
  2. Support for multiple locations.
  3. Configurable forecast length.
  4. Make the current weather available on a toolbar icon while running.

REPL|REBL: Driving I2C OLED displays with MicroPython — I2C monochrome displays with SSD1306

$
0
0

The following instructions are based on a 0.91in monochrome OLED, but other displays using the same chipset can be used with this library.

Requirements
Wemos D1 v2.2+ or good imitations.Buy 0.91in OLED Screen 128x32 pixels, I2c interface.Buy Breadboard Any size will do.Buy Wires Loose ends, or jumper leads.

Setting up

There is a Python ssd1306 module for OLED displays available in the MicroPython repository. Click Raw format and save the file with a .py extension.

You can then use the ampy tool (or the WebREPL) to upload the file to your device's filesystem:

ampy --port /dev/tty./dev/tty.wchusbserial141120 put ssd1306.py

With the ssd1306.py file on your Wemos D1, you can import it as any other Python module. Connect to your device, and then in the REPL enter:

frommachineimportI2C,Pinimportssd1306

If the import ssd1306 succeeds, the package is correctly uploaded and you're good to go.

Wire up the OLED display, connecting pins D1 to SCL and D2 to SDA. Provide power from G and 5V.

The circuit

Using the interface

To use the display you first need to create an I2C interface. In MicroPython I2C is via a software implementation so you can put it on any GPIO pins you like.

i2c=I2C(-1,Pin(5),Pin(4))display=ssd1306.SSD1306_I2C(128,32,i2c)

The SSD1306 module makes use of the MicroPython framebuf frame buffer, an efficient in-memory buffer for working with a simple graphics view. The methods for drawing text and primitives are from this framebuffer implementation. For a complete overview of what's available, check the MicroPython documentation.

The setup of the framebuffer format (monochrome, bit ordering, etc.) is also handled by the SSD1306 library. Check the framebuf documentation for more info on available options for other displays.

To test your I2C connection to the display, fill the display in solid colour.

display.fill(1)# Fill the entire display with 1="on"display.show()

You need to call display.show() to actually send the current framebuf to the device.

The display, filled

Drawing primitives

The .fill() method can be used to fill the entire display with a specified colour. Note that this changes all pixels to the given colour, and does not perform a flood fill of matching regions.

# Fill the entire display with colour 0display.fill(0)

The display, filled with blank

For filling specific regions of the display see .fill_rect() below.

Setting individual pixels can be accomplished using .pixel(). This is only a good idea when you area setting relatively few pixels as it is much slower than using other methods.

# Set the pixel at 3, 4 (x, y) to 1# .pixel(x, y, c)display.pixel(3,4,1)# 3rd param is the colourdisplay.show()

A single pixel

If you don't provide a color via parameter c, this method returns the colour of the pixel at the specified coordinates.

# Return the value at 3, 4 (x, y)# .pixel(x, y)c=display.pixel(3,4)

This is not a particularly nice API. In other cases (e.g. text) omitting the optional c will use 1 as a default.

Horizontal and vertical lines can be drawn with .hline() and .vline() respectively, providing a starting x,y location and line length and colour.

# Draw a horizontal line, starting from 2, 3 (x, y), 4 pixels wide # .hline(x, y, w, c)display.hline(2,3,25,1)display.show()

A horizontal line

# Draw a vertical line, starting from 5, 0 (x, y), 6 pixels high# .vline(x, y, h, c)display.vline(5,0,15,1)display.show()

A vertical line

For diagonal lines, the .line() method can be used to draw lines between between two sets of points x1,y1 and x2,y2 specified in order. The parameter c controls the colour of the line drawn.

# Draw a short diagonal line down to the right.# .line(x1, y1, x2, y2, c)display.line(0,0,50,25,1)display.show()

A diagonal line

There is no antialiasing, so diagonal lines will probably look pretty jaggy.

The .draw_rect() method allows you to draw a unfilled rectangle, starting at x,y and with a specified width w and height h. The specified colour c is used to draw the boundary of the rectangle, but it is not filled.

# Draw an unfilled rectangle of 8, 5 pixels, starting at 1,1 in colour 1.# .rect(x, y, w, h, c)display.rect(5,5,100,20,1)display.show()

An empty rectangle

You can also draw filled rectangles, using .fill_rect(). The parameters are the same as for .rect() but all pixels within the boundary will be set.

# Draw a filled rectangle of 10x5 pixels, starting at 3,3 in colour 1# .fill_rect(x, y, w, h, c)display.fill_rect(9,9,25,25,1)display.show()

An filled rectangle

Writing text

The framebuffer class provides support for writing text using a simple 8x8 bitmap font. The pixel x,y positions are relative to the top-left of the 8x8 character, so positioning at 0,0 will give the absolute top left of the screen.

# Print "Hello world!" 1 pixel from top left, in colour 1 (on)# .text(text, x, y, c)display.text("Hello world!",1,1,1)display.show()

Hello world

The .text() method takes an optional 4th parameter color, which gives the colour to draw text in. On a mono screen this can be either 0 (off) or 1 (on).

# Print "Hello world!" at 2,2 in colour 0 (off)display.text("Hello world!",2,2,0)display.show()

Hello world, black over

# Print "Hello world!" at 3,3 in colour 1 (on)display.text("Hello world!", 3, 3, 1)
display.show()

Hello world, white over black

Scrolling

You can shift the contents of the entire framebuffer around using .scroll, which takes x and y parameters to specify the scroll (positive/negative) in each dimension.

# .scroll(x, y)display.scroll(-10,0)

Scrolled left

There is no support for scrolling a portion of the framebuffer. You can however keep a separate framebuffer which you scroll and then blit to your main display — see the next section.

Pixel graphics

For very simple graphics that do not update often you can get away with writing bitmap graphics to the framebuffer pixel by pixel. For example, in the following code block we draw an icon from a list-of-lists of binary colour data, iterating over with simple loops:

ICON=[[0,0,0,0,0,0,0,0,0],[0,1,1,0,0,0,1,1,0],[1,1,1,1,0,1,1,1,1],[1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1],[0,1,1,1,1,1,1,1,0],[0,0,1,1,1,1,1,0,0],[0,0,0,1,1,1,0,0,0],[0,0,0,0,1,0,0,0,0],]display.fill(0)# Clear the displayfory,rowinenumerate(ICON):forx,cinenumerate(row):display.pixel(x,y,c)display.show()

Bitmap heart graphic

Using urandom we can scatter images over the display, like in the following example:

importurandomdefrandom_heart():xofs=urandom.getrandbits(8)yofs=urandom.getrandbits(5)fory,rowinenumerate(ICON):forx,cinenumerate(row):display.pixel(x+xofs,y+yofs,c)forninrange(100):random_heart()display.show()

Scattered heart graphics

If you can add an if c around the display.pixel call to only output pixels which are on — effectively masking, and avoiding the black square.

For bigger graphics or where you need faster updates, you can instead blit your bitmap images from one framebuffer to another. Pass the framebuf to blit, and the coordinates x and y to blit at.

# Blit a framebuffer at the pixel position 1, 1.# display.blit(fbuf, x, y)display.blit(fbuf,1,1)

The blit method takes an optional 4th parameter key which is a colour to considered 'transparent' when blitting. Pixels with this value won't be copied over onto the target display.

# Blit except pixels with value 0 (i.e. only pixels with value 1)# .blit(fbuf, x, y, key)display.blit(fbuf,1,1,key=0)

Display control

The drawing methods so far are inherited from the the MicroPython framebuf object, which is written to the display on .update(). But the ssd1306 object itself also provides methods for direct control of the display component.

The display can be turned on and off using display.poweron() and display.poweroff() respectively.

You can set the contrast for the display using .contrast() passing in parameter c which is a value between 0 and 255. This controls the contrast between the foreground, active colour 1 and the background 0.

# Set the display contrast to half (127/255 = 0.5)# .contrast(c)display.contrast(50)

Contrast 50

To invert the display, switching foreground and background colours, call .invert(1).

display.invert(1)

Inverted

Calling .invert(0) will return the display to how it was originally. You can use this for display-flashing visual effects:

importtimewhileTrue:display.invert(0)time.sleep(0.01)display.invert(1)time.sleep(0.01)

Python Bytes: #92 Will your Python be compiled?

Viewing all 22645 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>