25 Dec 2024

feedPlanet Python

Daniel Roy Greenfeld: Using locust for load testing

Locust is a Python library that makes it relatively straightforward to write Python tests. This heavily commented code example explains each section of code. To use locust:

  1. Install locust: pip install locust
  2. Copy the file below into the directory where you want to run locust
  3. In that directory, at the command-line, type: locust
  4. Open http://localhost:8089/
# locustfile.py
# For more options read the following
#   - https://docs.locust.io/en/stable/writing-a-locustfile.html
#   - https://docs.locust.io/en/stable/tasksets.html

# Import Locust basics
from locust import HttpUser, SequentialTaskSet, task, between

# Imports for generating content
from string import ascii_letters
from random import randint, shuffle

def namer():
    "Create a random string of letters under 10 characters long"
    ascii_list = list(ascii_letters)
    shuffle(ascii_list)
    return ''.join(ascii_list[:10])

class TaskSet(SequentialTaskSet):
    """
    A class for organizing tasks, inheriting from
    SequentialTaskSet means the tasks happen in order.
    """

    def on_start(self):
        # Methods with the on_start name will be called for each
        # simulated user when they start. Useful for logins and
        # other 'do before doing all other things'.
        pass

    def on_stop(self):
        # Methods with the on_stop name will be called for each
        # simulated user when they stop. Useful for logouts and
        # possibly data cleanup.
        pass    

    # TASKS!
    # Methods marked with the `@task` decorator is an action
    # taken by a user This example focuses on changes to a 
    # database, but provides a foundation for creating tests on
    # a more read-focused site

    @task
    def index(self):
        # User goes to the root of the project
        self.client.get('/')

    @task
    def create(self):
        # User posts a create form with the fields 'name'
        # and 'age'
        with self.client.post('/create', dict(name=namer(), age=randint(1,35))) as resp:
            self.pk =  resp.text

    @task
    def update(self):
        # User posts an update form with the fields 'name'
        # and 'age'"
        form_data = dict(id=self.pk, name=namer(), age=randint(1,35))
        self.client.post(f'/{self.pk}/update', form_data)

    @task
    def delete(self):
        # Represents the user getting a random ID and then
        # going to the delete page for it.
        self.client.get(f'/{self.pk}/delete')

class CatsiteUser(HttpUser):
    """
    This class represents simulated users interacting with
    a website.
    """
    # What tasks should be done    
    tasks = [TaskSet]
    # how long between clicks a user should take
    wait_time = between(2, 5)
    # The default host of the target client. This can be changed
    # at any time
    host = 'http://localhost:5001/'    

Sample test site

For reference, this is the test site used to create the above locustfile. I'll admit that the above test is incomplete, a lot more tasks could be added to hit web routes. To use it:

  1. Install FastHTML: pip install python-fasthtml
  2. Copy the file into the directory you want to run it
  3. In that directory, at the command-line, type: python cats.py
  4. Open http://localhost:5001/
# cats.py
from fasthtml.common import *

# Set up the database and table
db = database('cats.db')
class Cat: name:str; age:int; id:int
cats = db.create(Cat, pk='id', transform=True)

# Instantiate FastHTML app and route handler
app, rt = fast_app()

def mk_form(target: str):
    return Form(
            P(A('Home', href=index)),
            Fieldset(
                Input(name='name'),
                Input(name='age', type='number'),

            ),
            Input(type='submit', value='submit'),
            method='post'
        )

def cat_count():
    query = """select count(id) from cat;"""
    result = db.execute(query)
    return result.fetchone()[0]

@rt
def index():
    return Titled('Cats',
        P(A('Create cat', href='/create'), NotStr(' '), A('Random ID', href=random)),
        P(f'Number of cats: {cat_count()}'),        
        Ol(
            *[Li(A(f'{d.name}:{d.age}', href=f'/{d.id}')) for d in cats()]
        )
    )

@rt
def random():
    # Small datasets so we can get away with using the RANDOM() function here
    query = """SELECT id FROM cat ORDER BY RANDOM() LIMIT 1;"""
    result = db.execute(query)
    return result.fetchone()[0]

@rt('/create')
def get():
    return Titled('Create Cat', 
        mk_form('/create')
    )

@rt('/create')
def post(cat: Cat):
    while True:
        try:
            cat = cats.insert(Cat(name=cat.name, age=cat.age))
            break
        except Exception as e:
            print(e)
            raise
    return cat.id

@rt('/{id}')
def cat(id: int):
    cat = cats[id]
    return Titled(cat.name, 
        P(cat.age),
        P(A('update', href=f'/{id}/update')),
        P(A('delete', href=f'/{id}/delete')),
    )


@rt('/{id}/update')
def get(id: int):
    cat = cats[id]
    return Titled('Edit Cat',
        fill_form(mk_form(f'/{cat.id}/update'), cat)
    )

@rt('/{id}/update')
def post(cat: Cat, id: int):
    if id not in cats:
        return RedirectResponse(url=index)
    cat.id = id
    db.begin()
    try:
        cats.update(cat)
        db.commit()        
    except:
        db.rollback()
    return RedirectResponse(url=f'/{cat.id}')

@rt('/{id}/delete')
def cat(id: int):
    if id not in cats:
        RedirectResponse(url=index)
    # db.begin()
    cats.delete(id)
    # db.commit()
    return RedirectResponse(url=index)

serve()

Updates

25 Dec 2024 3:14am GMT

Daniel Roy Greenfeld: TIL: types.SimpleNamespace is a Bunch class

Did you know that Python's types library has a bunch class implementation? How did I not see this before?!

25 Dec 2024 3:14am GMT

Daniel Roy Greenfeld: TIL: run vs source

Run

A run launches a child process in a new bash within bash, so variables last only the lifetime of the command. This is why launching Python environments doesn't use run.

./list-things.sh

Source

A source is the current bash, so variables last beyond the running of a script. This is why launching Python environments use source.

source ~/.venv/bin/activate

25 Dec 2024 3:14am GMT

Daniel Roy Greenfeld: TIL: Using hx-swap-oob with FastHTML

Until now I didn't use this HTMX technique, but today Audrey Roy Greenfeld and I dove in together to figure it out. Note that we use language that may not match HTMX's description, sometimes it's better to put things into our own words so we understand it better.

from fasthtml.common import *

app,rt = fast_app()

def mk_row(name, email):
    return Tbody(
        # Only the Tr element and its children is being
        # injected, the Tbody isn't being injected
        Tr(Td(name), Td(email)),
        # This tells HTMX to inject this row at the end of
        # the #contacts-tbody DOM element
        hx_swap_oob="beforeend:#contacts-tbody",
    ),

@rt
def index():
    return Div(
            H2("Contacts"),
            Table(
                Thead(Tr(Th("Name"), Th("Email"))),
                Tbody(
                    Tr(Td("Audrey"), Td("mommy@example.com")),
                    Tr(Td("Uma"), Td("kid@example.com")),
                    Tr(Td("Daniel"), Td("daddy@example.com")),
                    # Identifies the contacts-tbody DOM element
                    id="contacts-tbody",
                ), 
            ),
            H2("Add a Contact"),
            Form(
                Label("Name", Input(name="name", type="text")),
                Label("Email", Input(name="email", type="email")),
                Button("Save"),
                hx_post="/contacts",
                # Don't swap out the contact form
                hx_swap='none',
                # Reset the form and put focus onto the name field
                hx_on__after_request="this.reset();this.name.focus();"
            )
        )

@rt
def contacts(name:str,email:str):
    print(f"Adding {name} and {email} to table")
    return mk_row(name,email)

serve()

To verify the behavior, view the rendered elements in your browser of choice before, after, and during submitting the form.

25 Dec 2024 3:14am GMT

Daniel Roy Greenfeld: TIL: SequentialTaskSet for Locust

SequentialTaskSet makes it so Locust tasks happen in a particular order, which ensures your simulated users are clicking around in a more human manner at a more human pace. Attribution goes to Audrey Roy Greenfeld.

You can see it in action in the now updated previous entry on the topic of Locust for load testing.

25 Dec 2024 3:14am GMT

Daniel Roy Greenfeld: TIL: Python's defaultdict takes a factory function

I've never really paid attention to this object but maybe I should have. It takes a single argument of a callable function. If you put in Python types it sets the default value to those types. For example, if I use an int at the instantiating argument then it gives us a zero.

>>> from collections import defaultdict
>>>
>>> mydict = defaultdict(int)
>>> print(mydict['anykey'])
0

Note that defaultdict also act like regular dictionaries, in that you can set keys. So mydict['me'] = 'danny' will work as you expect it to with a standard dictionary.

It gets more interesting if we pass in a more dynamic function. In the exmaple below we use random.randint and a lambda to make the default value be a random number between 1 and 100.

>>> from random import randint
>>>
>>> random_values = defaultdict(lambda: randint(1,100))

Let's try it out!

>>> for i in range(5):
>>>     print(random_values[i])
>>> print(random_values)
29
90
56
42
70
defaultdict(<function <lambda> at 0x72d292bb6de0>, {0: 29, 1: 90, 2: 56, 3: 42, 4: 70})

Attribution goes to Laksman Prasad, who pointing this out and encouraging me to closer look at defaultdict.

25 Dec 2024 3:14am GMT

Daniel Roy Greenfeld: TIL: Python Dictonary Merge Operator

The function way

Until today I did this:

# Make first dict
num_map = {
    'one': '1', 'two': '2', 'three': '3', 'four': '4',
    'five': '5', 'six': '6', 'seven': '7', 'eight': '8',
    'nine': '9'
}
# Add second dict
num_map.update({str(x):str(x) for x in range(1,10)})
print(num_map)

The operator way

Now thanks to Audrey Roy Greenfeld now I know I can do this:

# Make first dict while adding second dict
num_map = {
    'one': '1', 'two': '2', 'three': '3', 'four': '4',
    'five': '5', 'six': '6', 'seven': '7', 'eight': '8',
    'nine': '9'
} | {str(x):str(x) for x in range(1,10)}
print(num_map)

25 Dec 2024 3:14am GMT

Daniel Roy Greenfeld: TIL: Making pytest use Ipython's PDB

alias pdb='pytest --pdb --pdbcls=IPython.terminal.debugger:TerminalPdb'

Usage:

pdb tests/test_things::test_broken_thing

25 Dec 2024 3:14am GMT

Daniel Roy Greenfeld: TIL: How to reset Jupyter notebook passwords

jupyter notebook password

Attribution for this goes to Johno Whitaker.

25 Dec 2024 3:14am GMT

Daniel Roy Greenfeld: TIL: Fractional Indexing

In the past when I've done this for web pages and various other interfaces it has been a mess. I've built ungainly sort order in numeric or alphanumeric batches. Inevitably there is a conflict, often sooner rather than later. So sorting a list of things often means updating all the elements to preserve the order in the datastore. I've learned to mark each element with a big value, but it's ugly and ungainly

Fortunately for me, going forward, I now know about Fractional Indexing.

References:

25 Dec 2024 3:14am GMT

Daniel Roy Greenfeld: TIL: Autoreload for Jupyter notebooks

Add these commands to the top of a notebook within a Python cell. Thanks to Jeremy Howard for the tip.

%load_ext autoreload
%autoreload 2

25 Dec 2024 3:14am GMT

Daniel Roy Greenfeld: TIL: Arity

I'm excited to have learned there's a word for the count of arguments to a function/method/class: arity. Throughout my career I would have called this any of the following:

Thanks to Simon Willison for using it in a library or two and making me look up the word.

25 Dec 2024 3:14am GMT

24 Dec 2024

feedPlanet Python

PyCoder’s Weekly: Issue #661 (Dec. 24, 2024)

#661 - DECEMBER 24, 2024
View in Browser »

The PyCoder’s Weekly Logo


Exploring Modern Sentiment Analysis Approaches in Python

What are the current approaches for analyzing emotions within a piece of text? Which tools and Python packages should you use for sentiment analysis? This week, Jodie Burchell, developer advocate for data science at JetBrains, returns to the show to discuss modern sentiment analysis in Python.
REAL PYTHON podcast

Topological Sort

A Directed Acyclic Graph (DAG) is a common data structure used to contain a series of related items that must have certain order or dependency. Topological sorting is used to help find where you might start processing to get in order handling of the items in a DAG.
REDOWAN DELOWAR

Essential Python Web Security

This series explores the critical security principles every Python web developer needs. The first post delves into fundamental security best practices, ranging from general principles to specific Python-related techniques.
MICHAEL FORD

Quiz: How to Remove Items From Lists in Python

In this quiz, you'll test your understanding of removing items from lists in Python. This is a fundamental skill in Python programming, and mastering it will enable you to manipulate lists effectively.
REAL PYTHON

Python 3.14.0 Alpha 3 Is Out

CPYTHON DEV BLOG

Articles & Tutorials

Programming Sockets in Python

In this in-depth video course, you'll learn how to build a socket server and client with Python. By the end, you'll understand how to use the main functions and methods in Python's socket module to write your own networked client-server applications.
REAL PYTHON course

Python Decorators: A Super Useful Feature

Python decorators are one of Hashim's favorite features. This post covers some examples he's used in his projects. It includes the Prometheus Histogram Timing Decorators and OpenTelemetry (OTel) Manual Span Decorators.
HASHIM COLOMBOWALA

A Practical Example of the Pipeline Pattern in Python

"The pipeline design pattern (also known as Chain of Command pattern) is a flexible way to handle a sequence of actions, where each handler in the chain processes the input and passes it to the next handler."
JUAN JOSÉ EXPÓSITO GONZÁLEZ

Best Shift-Left Testing Tools to Improve Your QA

The later in your development process that you discover the bug the more expensive it is. Shift-Left Testing is a collection of techniques to attempt to move bug discovery earlier in your process.
ANTONELLO ZANINI

Merging Dictionaries in Python

There are multiple ways of merging two or more dictionaries in Python. This post teaches you how to do it and how to deal with corner cases like duplicate keys.
TREY HUNNER

Django Quiz 2024

Adam runs a quiz on Django at his Django London meetup. He's shared it so you can try it yourself. Test how much you know about your favorite web framework.
ADAM JOHNSON

Top Python Web Development Frameworks in 2025

This post compares many of the different web frameworks available for Python. It covers: Reflex, Django, Flask, Gradio, Streamlit, Dash, and FastAPI.
TOM GOTSMAN

My SQLAlchemy Cookbook

The post contains an embedded JupyterLite notebook containing a cookbook for SQLAlchemy. It focuses on the patterns you use in everyday ORM coding.
JAMIE CHANG

Django: Launch pdb When a Given SQL Query Runs

Here's a technique for using pdb within Django through hooking specific SQL queries. This uses database instrumentation in the Django ORM.
ADAM JOHNSON

Projects & Code

py-spy: Sampling Profiler for Python Programs

GITHUB.COM/BENFRED

markitdown: Convert Files and Office Documents to Markdown

GITHUB.COM/MICROSOFT

enlighten: Progress Bar for Python Console Apps

GITHUB.COM/ROCKHOPPER-TECHNOLOGIES

statsmodels: Statistical Modeling and Econometrics in Python

GITHUB.COM/STATSMODELS

htmy: Async, Pure-Python Rendering Engine

GITHUB.COM/VOLFPETER

Events

SPb Python Drinkup

December 26, 2024
MEETUP.COM

PyDelhi User Group Meetup

December 28, 2024
MEETUP.COM

PythOnRio Meetup

December 28, 2024
PYTHON.ORG.BR

Python Sheffield

December 31, 2024
GOOGLE.COM

STL Python

January 2, 2025
MEETUP.COM


Happy Pythoning!
This was PyCoder's Weekly Issue #661.
View in Browser »

alt


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

24 Dec 2024 7:30pm GMT

Django Weblog: Welcome to our new Django accessibility team members - Eli, Marijke, Saptak, Tushar

Sarah Abderemane, Thibaud Colas and Tom Carrick are pleased to introduce four new members in the Django Accessibility team ❤️.

Profile photo of Marijke, with greenery in the background. She smiles and wears a striped top
Marijke Luttekes
Profile photo of Eli, smiling, with a white top and striped jacket
Eliana Rosselli
Profile photo of Tushar. Smiling, he wears a gray shirt and is standing in front of a christmas tree
Tushar Gupta
Profile artwork of Saptak, with stylized avatar. He wears a blue top and an Octocat stands on his shoulder, holding a heart that says 'Drop ICE'
Saptak Sengupta

Marijke (pronounced Mah-Rye-Kuh) is a freelance web developer who creates human-friendly applications. She is based in Groningen, The Netherlands, specializing in Django, Python, HTML, SCSS, and vanilla JavaScript. She helps companies expand their existing projects, think about new features, train new developers, and improve developer team workflows. She is aDdjango contributor from the first session of Djangonaut Space program and she loves tea. You can learn more about Marijke on her website.

Eli is a full-stack developer from Uruguay who loves using Django and React. She is a Django contributor from the first session of the Djangonaut Space program

. She is passionate about good quality code, unit testing, and web accessibility. She enjoys drinking Maté (and talking about it!) and watching her football team play.

Tushar is a software engineer at Canonical, based in India. He got involved on open source during his studies loving the the supportive community. Through fellowships like Major League Hacking, Tushar dove into Django and took part in Djangonaut Space. Learn more about Tushar on his personal website.

Saptak is a self-proclaimed Human Rights Centered Developer Web. He focuses on security, privacy, accessibility, localization, and other human rights associated with websites that makes websites more inclusive and usable by everyone. Learn more about Saptak on his personal website.

Listen to them talking about their work

Here are recent talks or podcasts from our new team members if you want to get to know them better.

Marijke Luttekes - My path to becoming a Django contributor | pyGrunn 2024 Eli Rosselli - My step-by-step guide to becoming a Django core contributor | DjangoCon Europe 2024 Tushar Gupta - Djangonauts, Ready for Blast-Off | Talk Python to Me Ep.451 Saptak S - Accessibility for the Django Community | DjangoCon Europe 2024

What's next

In truth, our four new accessibility team members joined the team months ago - shortly after we published our 2023 accessibility team report. Up next, a lot of the team will be present at FOSDEM 2025, organizing, volunteering, or speaking at the Inclusive Web Devroom.

24 Dec 2024 2:00pm GMT

Talk Python to Me: #490: Django Ninja

If you're a Django developer, I'm sure you've heard so many people raving about FastAPI and Pydantic. But you really love Django and don't want to switch. Then you might want to give Django Ninja a serious look. Django Ninja is highly inspired by FastAPI, but is also deeply integrated into Django itself. We have Vitaliy Kucheryaviy the creator of Django Ninja on this show to tell us all about it.<br/> <br/> <strong>Episode sponsors</strong><br/> <br/> <a href='https://talkpython.fm/sentry'>Sentry Error Monitoring, Code TALKPYTHON</a><br> <a href='https://talkpython.fm/bluehost'>Bluehost</a><br> <a href='https://talkpython.fm/training'>Talk Python Courses</a><br/> <br/> <h2>Links from the show</h2> <div><strong>Vitaly</strong>: <a href="https://github.com/vitalik?featured_on=talkpython" target="_blank" >github.com/vitalik</a><br/> <strong>Vitaly on X</strong>: <a href="https://x.com/vital1k?featured_on=talkpython" target="_blank" >@vital1k</a><br/> <br/> <strong>Top 5 Episodes of 2024</strong>: <a href="https://talkpython.fm/blog/posts/top-talk-python-podcast-episodes-of-2024/" target="_blank" >talkpython.fm/blog/posts/top-talk-python-podcast-episodes-of-2024</a><br/> <br/> <strong>Django Ninja</strong>: <a href="https://django-ninja.dev/?featured_on=talkpython" target="_blank" >django-ninja.dev</a><br/> <strong>Motivation section we talked through</strong>: <a href="https://django-ninja.dev/motivation/?featured_on=talkpython" target="_blank" >django-ninja.dev/motivation</a><br/> <strong>LLM for Django Ninja</strong>: <a href="https://llm.django-ninja.dev/?featured_on=talkpython" target="_blank" >llm.django-ninja.dev</a><br/> <strong>Nano Django</strong>: <a href="https://github.com/vitalik/nano-django?featured_on=talkpython" target="_blank" >github.com/vitalik/nano-django</a><br/> <strong>Episode transcripts</strong>: <a href="https://talkpython.fm/episodes/transcript/490/django-ninja" target="_blank" >talkpython.fm</a><br/> <br/> <strong>--- Stay in touch with us ---</strong><br/> <strong>Subscribe to Talk Python on YouTube</strong>: <a href="https://talkpython.fm/youtube" target="_blank" >youtube.com</a><br/> <strong>Talk Python on Bluesky</strong>: <a href="https://bsky.app/profile/talkpython.fm" target="_blank" >@talkpython.fm at bsky.app</a><br/> <strong>Talk Python on Mastodon</strong>: <a href="https://fosstodon.org/web/@talkpython" target="_blank" ><i class="fa-brands fa-mastodon"></i>talkpython</a><br/> <strong>Michael on Bluesky</strong>: <a href="https://bsky.app/profile/mkennedy.codes?featured_on=talkpython" target="_blank" >@mkennedy.codes at bsky.app</a><br/> <strong>Michael on Mastodon</strong>: <a href="https://fosstodon.org/web/@mkennedy" target="_blank" ><i class="fa-brands fa-mastodon"></i>mkennedy</a><br/></div>

24 Dec 2024 8:00am GMT

23 Dec 2024

feedPlanet Python

Daniel Roy Greenfeld: TIL: Using Python to removing prefixes and suffixes

Starting in Python 3.9, s.removeprefix() and s.removesuffix() were added as str built-ins. Which easily covers all the versions of Python I currently support.

Usage for removeprefix():

>>> 'Spam, Spam'.removeprefix('Spam')
', Spam'
>>> 'Spam, Spam'.removeprefix('This is not in the prefix')
'Spam, Spam'

Usage for removesuffix():

>>> 'Spam, Spam'.removesuffix('Spam')
'Spam, '
>>> 'Spam, Spam'.removesuffix('This is not in the suffix')
'Spam, Spam'

23 Dec 2024 7:03pm GMT