Showing posts with label Python Tips. Show all posts
Showing posts with label Python Tips. Show all posts

Wednesday, September 18, 2019

Filtering & Closing Pull Requests on GitHub using the API

Hi everyone! ๐Ÿ‘‹ In this post, I am going to show you how you can use the GitHub API to query Pull Requests, check the content of a PR and close it.

The motivation for this project came from my personal website. I introduced static comments on the website using Staticman and only after a day or two, got bombarded with spam. I hadn’t enabled Akismet or any honey pot field so it was kinda expected. However, this resulted in me getting 200+ PRs on GitHub for bogus comments which were mainly advertisements for amoxicillin (this was also the first time I found out how famous this medicine is).

I was in no mood for going through the PRs manually so I decided to write a short script which went through them on my behalf and closed the PRs which mentioned certain keywords.

You can see the different PRs opened by staticman. Most of these are spam:

Screen Shot 2019-09-17 at 8.58.54 PM.png

For this project, I decided to use PyGithub library. It is super easy to install it using pip:

pip install pygithub

Now we can go ahead and log in to GitHub using PyGithub. Write the following code in a github_clean.py file:

from github import Github
import argparse

def parse_arguments():
    """
    Parses arguments
    """
    parser = argparse.ArgumentParser()
    parser.add_argument('-u', '--username', 
        required=True, help="GitHub username")
    parser.add_argument('-p', '--password', 
        required=True, help="GitHub password")
    parser.add_argument('-r', '--repository', 
        required=True, help="repository name")
    parsed_args = parser.parse_args()
    if "/" not in parsed_args.repository:
        logging.error("repo name should also contain username like: username/repo_name")
        sys.exit()
    return parsed_args
    
def main():
    args = parse_arguments()
    g = Github(args.username, args.password)
    
if __name__ == '__main__':
    main()

So far I am just using argparse to accept and parse the command line arguments and then using the arguments to create a Github object.

You will be passing in three arguments:

  1. Your GitHub username
  2. Your GitHub password
  3. The repo you want to work with

Next step is to figure out how to loop through all the pull requests and check if their body contains any “spam” words:

repo =  g.get_repo(args.repository)
issues = repo.get_issues()

page_num = 0
while True:
    issue_page = issues.get_page(page_num)
    if issue_page == []:
        break
    for issue in issue_page:
        # Do something with the individual issue
        if spam_word in issue.raw_data['body'].lower():
            print("Contains spam word!!")

First, we query GitHub for a specific repo using g.get_repo and then we query for issues for that repo using repo.get_issues. It is important to note that all PRs are registered as issues as well so querying for issues will return pull requests as well. GitHub returns a paginated result so we just continue asking for successive issues in a while loop until we get an empty page.

We can check the body of an issue (PR) using issue.raw_data['body']. Two important pieces are missing from the above code. One is the spam_word variable and another is some sort of a mechanism to close an issue.

For the spam_word, I took a look at some issues and created a list of some pretty frequent spam words. This is the list I came up with:

spam_words = ["buy", "amoxi", "order", "tablets", 
"pills", "cheap", "viagra", "forex", "cafergot", 
"kamagra", "hacker", "python training"]

Add this list at the top of your github_clean.py file and modify the if statement like this:

closed = False
if any(spam_word in issue.raw_data['body'].lower() for spam_word in spam_words):
    issue.edit(state="closed")
    closed = True
print(f"{issue.number}, closed: {closed}")

With this final snippet of code, we have everything we need. My favourite function in this code snippet is any. It checks if any of the elements being passed in as part of the argument is True.

This is what your whole file should look like:

import argparse
import sys
import re
import logging

from github import Github

spam_words = ["buy", "amoxi", "order", "tablets", 
"pills", "cheap", "viagra", "forex", "cafergot", 
"kamagra", "hacker", "python training"]
logging.basicConfig(level=logging.INFO)

def parse_arguments():
    """
    Parses arguments
    """
    parser = argparse.ArgumentParser()
    parser.add_argument('-u', '--username', 
        required=True, help="GitHub username")
    parser.add_argument('-p', '--password', 
        required=True, help="GitHub password")
    parser.add_argument('-r', '--repository', 
        required=True, help="repository name")
    parsed_args = parser.parse_args()
    if "/" not in parsed_args.repository:
        logging.error("repo name should also contain username like: username/repo_name")
        sys.exit()
    return parsed_args

def process_issue(issue):
    """
    Processes each issue and closes it 
    based on the spam_words list
    """
    closed = False
    if any(bad_word in issue.raw_data['body'].lower() for bad_word in words):
        issue.edit(state="closed")
        closed = True
    return closed

def main():
    """
    Coordinates the flow of the whole program
    """
    args = parse_arguments()
    g = Github(args.username, args.password)
    logging.info("successfully logged in")
    repo =  g.get_repo(args.repository)

    logging.info("getting issues list")
    issues = repo.get_issues()

    page_num = 0
    while True:
        issue_page = issues.get_page(page_num)
        if issue_page == []:
            logging.info("No more issues to process")
            break
        for issue in issue_page:
            closed = process_issue(issue)
            logging.info(f"{issue.number}, closed: {closed}")
        page_num += 1

    
if __name__ == '__main__':
    main()

I just added a couple of different things to this script, like the logging. If you want, you can create a new command-line argument and use that to control the log level. It isn’t really useful here because we don’t have a lot of different log levels.

Now if you run this script you should see something similar to this:

INFO:root:successfully logged in
INFO:root:getting issues list
INFO:root:No more issues to process

It doesn’t process anything in this run because I have already run this script once and there are no more spam issues left.

So there you go! I hope you had fun making this! If you have any questions/comments/suggestions please let me know in the comments below! See you in the next post ๐Ÿ™‚ ♥

 



from Python Tips
read more

Tuesday, September 17, 2019

Looking for an internship for Summer 2020

Hi lovely people! ๐Ÿ‘‹ Hope everything is going well on your end. I asked you guys last year for helping me find a kick-ass internship and you all came through. I ended up working at ASAPP over the summer and had an awesome time. I wrote an article about what I learned during my internship.

I am putting out the same request for next summer as well. If you have benefited from any of my articles and work at an amazing company and feel like I would be a good addition to your team, please reach out. I am looking for a 12-14 week internship. I strongly prefer small teams where I can bond with the people I am working with. I am open to most places but bonus points if you work at a hardware based tech company or a fintech startup. However, this is not a hard requirement.

I have done a lot of backend development in Python and GoLang. I am fairly comfortable with dabbling in the front-end code as well. I have also tinkered with open source hardware (Arduino & Raspberry Pi) and wrote a couple of articles about what I did and how I did it. You can take a look at my resume (PDF) to get a better understanding of my expertise. You can also read about how I got into programming through this article.

I am ok with take-home assignments and kinda prefer them to algorithm interviews. Bonus points if your company does that but again, not a hard requirement. I know take-homes take a lot of time but IMO they gauge the proficiency better than a normal algorithm interview.

I hope you guys would come through this time as well. Have a fantastic day and keep smiling. If you have any questions/comments/suggestions, please comment below or send me an email at yasoob.khld at gmail.com.

See ya! ♥

 



from Python Tips
read more

Friday, June 21, 2019

Setting up dev environment for SciPy

Hi everyone! ๐Ÿ‘‹

I got an email from someone pretty recently who wanted to setup a dev environment for SciPy. He had made changes to the source code of SciPy and now wanted to test if his changes were working or not. He had gotten so far without actually testing the code. In this post I will share details on how to setup a dev environment the right way. I will focus mainly on Mac OS.

Firstly, go to the GitHub repo and try to figure out the dependencies for the project. Normally they are listed in the readme file. If they are not listed there then you just try installing the package/libary and the errors in the terminal will give you a clue as to what you are missing. I did that and figured out that I needed Fortran compiler, Cython and NumPy.

Installing dependencies:

Let’s start with Fortran:

brew install gcc

Now create a new folder and setup a virtualenv there:

mkdir ~/dev
cd ~/dev
python -m venv env

Activate the virtualenv:

source env/bin/activate

Now install Cython and NumPy:

pip install cython
pip install numpy

Now clone SciPy:

git clone git@github.com:scipy/scipy.git

And finally install SciPy in development mode:

cd scipy
python setup.py develop

Normally if you are installing a Python package using the setup.py file, you use python setup.py install. This copies the code into the site-packages directory. After that if you make any changes to the source code of the package, you need to run python setup.py install each time.

The difference between that and python setup.py develop is that in the later case Python does not copy the code to site-packages. It uses the code from that development folder directly whenever you import the package. This way if you make any changes to the package you don’t need to run python setup.py install or python setup.py develop.

After you are done with the development you can safely type deactivate and this will turn off the virtualenv.

You can read more about virtualenv on Real Python. I hope someone out there in the same boat as one of my other readers finds this helpful.

Have a good day! ❤

 



from Python Tips
read more

Wednesday, May 29, 2019

Speeding up Python code using multithreading

Hi lovely people! ๐Ÿ‘‹ A lot of times we end up writing code in Python which does remote requests or reads multiple files or does processing on some data. And in a lot of those cases I have seen programmers using a simple for loop which takes forever to finish executing. For example:

import requests
from time import time

url_list = [
    "https://via.placeholder.com/400",
    "https://via.placeholder.com/410",
    "https://via.placeholder.com/420",
    "https://via.placeholder.com/430",
    "https://via.placeholder.com/440",
    "https://via.placeholder.com/450",
    "https://via.placeholder.com/460",
    "https://via.placeholder.com/470",
    "https://via.placeholder.com/480",
    "https://via.placeholder.com/490",
    "https://via.placeholder.com/500",
    "https://via.placeholder.com/510",
    "https://via.placeholder.com/520",
    "https://via.placeholder.com/530",
]

def download_file(url):
    html = requests.get(url, stream=True)
    return html.status_code

start = time()

for url in url_list:
    print(download_file(url))

print(f'Time taken: {time() - start}')

Output:

<--truncated-->
Time taken: 4.128157138824463

This is a sane example and the code will open each URL, wait for it to load, print its status code and only then move on to the next URL. This kind of code is a very good candidate for multi-threading.

Modern systems can run a lot of threads and that means you can do multiple tasks at once with a very low over-head. Why don’t we try and make use of that to make the above code process these URLs faster?

We will make use of the ThreadPoolExecutor from the concurrent.futures library. It is super easy to use. Let me show you some code and then explain how it works.

import requests
from concurrent.futures import ThreadPoolExecutor, as_completed
from time import time

url_list = [
    "https://via.placeholder.com/400",
    "https://via.placeholder.com/410",
    "https://via.placeholder.com/420",
    "https://via.placeholder.com/430",
    "https://via.placeholder.com/440",
    "https://via.placeholder.com/450",
    "https://via.placeholder.com/460",
    "https://via.placeholder.com/470",
    "https://via.placeholder.com/480",
    "https://via.placeholder.com/490",
    "https://via.placeholder.com/500",
    "https://via.placeholder.com/510",
    "https://via.placeholder.com/520",
    "https://via.placeholder.com/530",
]

def download_file(url):
    html = requests.get(url, stream=True)
    return html.status_code

start = time()

processes = []
with ThreadPoolExecutor(max_workers=10) as executor:
    for url in url_list:
        processes.append(executor.submit(download_file, url))

for task in as_completed(processes):
    print(task.result())


print(f'Time taken: {time() - start}')

Output:

<--truncated-->
Time taken: 0.4583399295806885

We just sped up our code by a factor of almost 9! And we didn’t even do anything super involved. The performance benefits would have been even more if there were more urls.

So what is happening? When we call executor.submit we are adding a new task to the thread pool. We store that task in the processes list. Later we iterate over the processes and print out the result.

The as_completed method yields the items (tasks) from processes list as soon as they complete. There are two reasons a task can go to the completed state. It has either finished executing or it got cancelled. We could have also passed in a timeout parameter to as_completed and if a task took longer than that time period, even then as_completed will yield that task.

You should explore multi-threading a bit more. For trivial projects it is the quickest way to speed up your code. If you want to learn, more read the official docs. They are super helpful.

Have a good day! See you later!



from Python Tips
read more

Monday, February 25, 2019

Python dis module and constant folding

Hi people! Recently, I was super confused when I found out that:

>>> pow(3,89)

runs slower than:

>>> 3**89

I tried to think of a suitable answer but couldn’t find any. I timed the execution of both of these statements using the timeit module in Python3:

$ python3 -m timeit 'pow(3,89)'
500000 loops, best of 5: 688 nsec per loop

$ python3 -m timeit '3**89'
500000 loops, best of 5: 519 nsec per loop

The difference is not big. It is only 0.1ยตs but it was still bugging me. If I can’t explain something in programming, I usually end up having sleepless nights ๐Ÿ˜…

I found the answer through the Python IRC channel on Freenode. The reason why pow is slightly slower is that in CPython there is an additional step of loading pow from the namespace. Whereas, in 3**9 there is no such loading required. This also means that the difference will remain more or less constant even if the input numbers get bigger and bigger.

The hypothesis is true:

$ python3 -m timeit 'pow(3,9999)'
5000 loops, best of 5: 58.5 usec per loop

$ python3 -m timeit '3**9999'
5000 loops, best of 5: 57.3 usec per loop

During the process of exploring the solution to this question I also got to learn about the dis module. It allows you to decompile the Python Bytecode and inspect it. This was a super exciting discovery mainly because I am learning more about reverse engineering binaries now-a-days and this module fits right in.

I disassembled the bytecode of the above statements like this in Python:

>>> import dis
>>> dis.dis('pow(3,89)')
#  1           0 LOAD_NAME                0 (pow)
#              2 LOAD_CONST               0 (3)
#              4 LOAD_CONST               1 (89)
#              6 CALL_FUNCTION            2
#              8 RETURN_VALUE

>>> dis.dis('3**64')
#  1           0 LOAD_CONST               0 (3433683820292512484657849089281)
#              2 RETURN_VALUE

>>> dis.dis('3**65')
#  1           0 LOAD_CONST               0 (3)
#              2 LOAD_CONST               1 (65)
#              4 BINARY_POWER
#              6 RETURN_VALUE

You can learn about how to understand the output of dis.dis by reading this answer on Stackoverflow

Ok back to the code. The disassembly of pow makes sense. It is loading pow from the namespace and then loading 3 and 89 to registers and finally calling the pow function. But why does the output of the next two disassemblies differ from each other? The only thing we changed is the exponent value from 64 to 65!

This question introduced me to another new concept of “constant folding“. It just means that when we have a constant expression Python evaluates the value of that expression at compile time so that when you actually run the program it doesn’t take as long to run because Python uses the already computed value. Think of it like this:

def one_plue_one():
    return 1+1

# --vs--

def one_plue_one():
    return 2

Python compiles the first function to the second one and uses that when you run the code. Neat, eh?

So why is constant folding working for 3**64 and not 3**65? Well, I don’t know. This probably has something to do with a limit to how many powers the system has pre-computed in memory. I can be totally wrong. The next step in my mind is to dabble in the Python source code in my free time and try to figure out what is happening. I am still trying to figure out an answer for this so if you have any suggestions please drop them in the comments section below.

What I want you to take away from this post is that you should seek solutions to basic questions. You never know where the answers will lead you. You might end up learning something entirely new (like I just did)! I hope you guys keep this flame of curiosity burning. See you later ๐Ÿ™‚

PS: If you want to learn more about Python Bytecode and how to play around with it, someone (njs on #python) suggested me this talk. I personally haven’t watched it but I probably will once I get some free time.



from Python Tips
read more

Monday, February 4, 2019

Issues with how we teach

Throughout my life, I used to ask myself, “How do people invent something?”. In my case, I was specifically concerned with Maths, Physics, and Computer Science.

I would ask myself, “I know how to use these formulas but how did someone come up with these formulas?”.

My teachers’ responses were always the same: “Don’t worry you will understand how it works later on. For now, just learn how to use this formula”. Okay, that was simple. I was already doing it. Learning a bunch of formulas without realizing “what” or “who” they were.

I am talking about even simple things like ฯ€. We encounter ฯ€ in almost every calculation which involves circles but I was never taught what ฯ€ “is”. I was forced to take a stab at questions directly. My learning never began with the concept that ฯ€ is the ratio between the circumference and the radius of a circle.

Another example is “i”. I learned that “i” is used to differentiate imaginary numbers from real numbers. What I never learned was how “i” just changes the “direction” we are moving in on our graph. Once it clicks, Euler’s theorem e^(iฯ€) = -1 starts making more sense. Circular motion makes more sense and waves make more sense.

What we are taught are the tools but not the theory behind the tools. If we don’t learn how the basic tools are made then how can we expect someone to improve or invent new tools in any meaningful way?

Everything in Maths, Physics and Computer Science is connected. All the formulas and theorems are derived from the basic concepts we already know and are familiar with.

I used to believe that this is exactly how I am supposed to be taught. I am just not smart enough to figure out the relationship between all of this stuff myself.

I was proved wrong by the book “Surely You’re Joking, Mr. Feynman!”.

Let me relate one incident where Feynman was invited to give a bunch of lectures in Brazil. During the course of his stay, he learned that the students knew all the formulas by heart but they couldn’t “see” the formulas in real life. He asked them about light polarization in a lake and asked a related question. They were blank. He asked them about polarization in general, they knew every theorem.

What the students failed to do was to observe that lake itself was acting as a mirror. They were unable to make the connection and solve the question which Feynman gave them! Feynman was talking to students who were like me. They knew the theorems but not the intuitiveness and applicability of those theorems! They simply had to apply the theorems they knew by heart in a different setting and they were unable to do it.

This is just one of the interactions of Feynman with students in Brazil. There are a whole lot more in the book.

At the end of his stay, the students asked Feynman to say a few words regarding his stay in Brazil. Feynman agreed to do it only if he was free to say whatever he wanted. He got the approval and hence a big event was organized before he left Brazil. All the famous politicians, educationalists, and students were in attendance.

Feynman got up on stage and said: “No science is being taught in Brazil”.

He backs up his claim by his experience in interacting with the students. He gave specific and convincing examples on why he thinks that is the case. I would highly recommend everyone to give his book a read and specifically focus on the “O Americano, Outra Vez!” chapter.

What is my aim?

I might get a lot of criticism by making it sound as if my professors did not teach me anything. That is not the case! They are all learned and amazing people who taught me everything I know. I just want them to focus more on the intuitiveness of the questions than the theorems themselves. Make the student appreciate the theorem before forcing him to learn the definitions. If the students start appreciating how everything is connected, learning comes naturally and things become more interesting.

What am I planning on doing now?

I am going to try and learn everything “intuitively” first, rather than cramming a bunch of algorithms and ideas in my mind. I will try to make sure I “understand” something before I apply it in practice. It is a very hard task, especially when your whole life you have been used to just learning formulas and applying them.

If you want to look at maths from an intuitive perspective then read the articles on Better Explained by Khalid Azad and watch the videos by 3Blue1Brown on YouTube.

Is the future challenging? Yes. Am I looking forward to it? Absolutely!



from Python Tips
read more

Saturday, December 1, 2018

Email Security & Privacy

Hi everyone! I hope all of you are doing well. Things have been super exciting on my side. Just got done with a file system checker project in C for my Operating Systems class. It was mentally draining but equally rewarding. This blog post is not about Python but rather about Emails.

This week I also gave a department wide talk at Colgate University on Email security and privacy and some of the SMTP features which are available at your disposal. It went fairly successful. This was a result of my own independent research on the topic. I tried to cover as much base as possible while also trying to keep the talk engaging.

If you want to learn more about the topic, you can take a look at the slides I used and do some research of your own. If something seems wrong please let me know in the comments below so that we all can benefit. You can also access (I think?) the speaker notes for more context behind the content on the slides.

If you are pressed for time, skip to the last slide to learn about some interesting attacks. One of them makes use of Cyrillic script. If you haven’t heard of Cyrillic script before you would love that slide.

I would love to hear from you all. Please share your views in the comments below about the talk or anything in general.



from Python Tips
read more

TestDriven.io: Working with Static and Media Files in Django

This article looks at how to work with static and media files in a Django project, locally and in production. from Planet Python via read...