25 Dec 2024
Planet 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:
- Install locust:
pip install locust
- Copy the file below into the directory where you want to run locust
- In that directory, at the command-line, type:
locust
- 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:
- Install FastHTML:
pip install python-fasthtml
- Copy the file into the directory you want to run it
- In that directory, at the command-line, type:
python cats.py
- 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
- 2024-11-08 Use
SequentialTaskSet
as recommended by Audrey Roy Greenfeld - 2024-11-08 Fixed a few bugs in cats.py
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:
- https://www.figma.com/blog/realtime-editing-of-ordered-sequences/
- https://observablehq.com/@dgreensp/implementing-fractional-indexing
- https://github.com/httpie/fractional-indexing-python
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:
number_of_args
param_count
numargs
intArgumentCount
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
Planet Python
PyCoder’s Weekly: Issue #661 (Dec. 24, 2024)
#661 - DECEMBER 24, 2024
View in Browser »
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
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
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 »
[ 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 ❤️.
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.
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
Planet 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