shot-scraper: automated screenshots for documentation, built on Playwright
shot-scraper is a new tool that I’ve built to help automate the process of keeping screenshots up-to-date in my documentation. It also doubles as a scraping tool—hence the name—which I …
Simon Willison’s Weblog · simonwillison.net [1]
An interesting way to build automatically annotaatd docs with arrows pointing to elements on a webpage.
Note
This post is a thought [2]. It’s a short note that I make
about someone else’s content online #thoughts
References:
[1]: https://simonwillison.net/2022/Mar/10/shot-scraper/#a-complex-example
[2]: /thoughts/
Posts tagged: python
All posts with the tag "python"
312 posts
latest post 2026-05-06
Publishing rhythm
Create Models with a Many-to-Many Link - SQLModel
SQLModel, SQL databases in Python, designed for simplicity, compatibility, and robustness.
sqlmodel.tiangolo.com [1]
Creating many to many relationships with sqlmodel requires a LinkTable Model. The link model will keep track of the linked id’s between each of the models.
[2]
from typing import List, Optional
from sqlmodel import Field, Relationship, Session, SQLModel, create_engine
class HeroTeamLink(SQLModel, table=True):
team_id: Optional[int] = Field(
default=None, foreign_key="team.id", primary_key=True
)
hero_id: Optional[int] = Field(
default=None, foreign_key="hero.id", primary_key=True
)
class Team(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
name: str = Field(index=True)
headquarters: str
heroes: List["Hero"] = Relationship(back_populates="teams", link_model=HeroTeamLink)
class Hero(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
name: str = Field(index=True)
secret_name: str
age: Optional[int] = Field(default=None, index=True)
teams: List[Team] = Relationship(back_populates="heroes", link_model=HeroTeamLink)
Note
This post...
External Link
stackoverflow.com [1]
I went down the route of leveraging the json-enc extention in htmx [2], but later realized that this completely breaks browsers/users who do not wish to use javascript. While most of the web would feel quite broken with javascript disabled, I don’t want to contribute to that without good reason.
Taking a second look into this issue, rather than using json-enc, and using as_form to get form data into a model keeps the nice DX fo everything being a pydantic model, but the site still works without js. with js htmx kicks in, you get a spa like experience by loading partials onto the page, and without, you just get a full page reload.
the implementation # [3]
copied from https://stackoverflow.com/questions/60127234/how-to-use-a-pydantic-model-with-form-data-in-fastapi
import inspect
from typing import Type
from fastapi import Form
from pydantic import BaseModel
from pydantic.fields import ModelField
def as_form(cls: Type[BaseModel]):
new_parameters = []
for field_name, model_field in cls.__fields__.items():
model_field: ModelField # type: ignore
new_parameters.append(
inspect.Parameter(
model_field.alias,
inspect.Parameter.POSITION...
[1]
How to enable debug mode in pywebview.
import webview
webview.create_window('Woah dude!', 'https://pywebview.flowrl.com/hello')
webview.start(debug=True)
Note
This post is a thought [2]. It’s a short note that I make
about someone else’s content online #thoughts
References:
[1]: /static/https://pywebview.flowrl.com/guide/debugging.html
[2]: /thoughts/
`ValueError: Constraint must have a name` in alembic 1.10.0 · Issue #1195 · sqlalchemy/alembic
Describe the bug ValueError: Constraint must have a name in alembic 1.10.0. Expected behavior Migration succeeds. To Reproduce Please try to provide a Minimal, Complete, and Verifiable example, wit...
GitHub · github.com [1]
After a nasty time with alembic upgrades, thoughts is about to get a new users table. This may have came from incorrectly setting up alembic for sqlite from the start, but I was able to fix the issue with this GitHub issue.
alembic sqlite ValueError: Constraint must have a name
The change I needed to make to get my migration to run.
+ batch_op.create_foreign_key('fk_post_author_id_user', 'user', ['author_id'], ['id'])
Note
This post is a thought [2]. It’s a short note that I make
about someone else’s content online #thoughts
References:
[1]: https://github.com/sqlalchemy/alembic/issues/1195
[2]: /thoughts/
![[None]]
Since using alembic I have been just running out a new revision checking its content and deleting it if its empty, today I learned there is an alembic check command to check for operations that need to be created.
❯ alembic check
INFO [alembic.runtime.migration] Context impl SQLiteImpl.
INFO [alembic.runtime.migration] Will assume non-transactional DDL.
No new upgrade operations detected.
Note
This post is a thought [1]. It’s a short note that I make
about someone else’s content online #thoughts
References:
[1]: /thoughts/
API — Jinja Documentation (3.1.x)
jinja.palletsprojects.com [1]
🤯 jinja comes with a loader to pre-compile templates! Defihnitely need to look at this for markata, as jinja is till one of the biggest hot spots.
Note
This post is a thought [2]. It’s a short note that I make
about someone else’s content online #thoughts
References:
[1]: https://jinja.palletsprojects.com/en/3.0.x/api/#jinja2.Environment.compile_templates
[2]: /thoughts/
[1]
I’ve definitely been missing out on setting up a proper jinja loader on a few projects, I need to lean on this a bit more.
class jinja2.FileSystemLoader(searchpath, encoding='utf-8', followlinks=False):
'''
Load templates from a directory in the file system.
'''
The path can be relative or absolute. Relative paths are relative to the current working directory.
loader = FileSystemLoader("templates")
# A list of paths can be given. The directories will be searched in order, stopping at the first matching template.
loader = FileSystemLoader(["/override/templates", "/default/templates"])
Note
This post is a thought [2]. It’s a short note that I make
about someone else’s content online #thoughts
References:
[1]: /static/https://jinja.palletsprojects.com/en/3.0.x/api/#jinja2.FileSystemLoader
[2]: /thoughts/
Read a Range of Data - LIMIT and OFFSET - SQLModel
SQLModel, SQL databases in Python, designed for simplicity, compatibility, and robustness.
sqlmodel.tiangolo.com [1]
Implement paging in sqlmodel with where, limit, and offset.
def select_heroes():
with Session(engine) as session:
statement = select(Hero).where(Hero.age > 32).limit(3)
results = session.exec(statement)
heroes = results.all()
print(heroes)
Note
This post is a thought [2]. It’s a short note that I make
about someone else’s content online #thoughts
References:
[1]: https://sqlmodel.tiangolo.com/tutorial/limit-and-offset/?h=#combine-limit-and-offset-with-where
[2]: /thoughts/
DuckDB vs. MotherDuck: When to Move to the Cloud | Kestra
DuckDB is fast and free. MotherDuck adds cloud storage, collaboration, and scale. Here
kestra.io [1]
duckdb is a new in process database that has been making its rounds in analytics for its high performance in those applications.
Mother duck is a centeralized server that brings manages storage, data sharing and an ide to duckdb.
Note
This post is a thought [2]. It’s a short note that I make
about someone else’s content online #thoughts
References:
[1]: https://kestra.io/blogs/2023-07-28-duckdb-vs-motherduck
[2]: /thoughts/
Textual - Center things
Textual is a TUI framework for Python, inspired by modern web development.
Textual Documentation · textual.textualize.io [1]
How to center things in textual. Textual has a very unique way of styling text user interfaces for the terminal using css. If you know css it feels natural.
@willmcgugan [2], has put together a great article on how to center things in textual
here the final result
from textual.app import App, ComposeResult
from textual.widgets import Static
QUOTE = "Could not find you in Seattle and no terminal is in operation at your classified address."
class CenterApp(App):
"""How to center things."""
CSS = """
Screen {
align: center middle;
}
#hello {
background: blue 50%;
border: wide white;
width: 40;
height: 9;
text-align: center;
content-align: center middle;
}
"""
def compose(self) -> ComposeResult:
yield Static(QUOTE, id="hello")
if __name__ == "__main__":
app = CenterApp()
app.run()
Note
This post is a thought [3]. It’s a short note that I make
about someone else’s content online #thoughts
References:
[1]: https://textual.textualize.io/how-to/center-things/
[2]: https://willmcgugan.github.io
[3]: /thoughts/
Client Challenge
pypi.org [1]
Super useful way to show a tree view of an s3 bucket’s structure!
pip install s3-tree
s3-tree bucketname
Note
This post is a thought [2]. It’s a short note that I make
about someone else’s content online #thoughts
References:
[1]: https://pypi.org/project/s3-tree/
[2]: /thoughts/
External Link
stackoverflow.com [1]
How to sort results from a sqlalchemy based orm.
.order_by(model.Entry.amount.desc())
I needed this to enable paging on my thoughts api.
@post_router.get("/posts/")
async def get_posts(
*,
request: Request,
session: Session = Depends(get_session),
hx_request: Annotated[str | None, Header()] = None,
accept: Annotated[str | None, Header()] = None,
current_user: Annotated[User, Depends(try_get_current_active_user)],
page_size: int = 10,
page: int = 1,
) -> Posts:
"get all posts"
statement = (
select(Post)
.where(Post.published)
.order_by(Post.id.desc())
.limit(page_size)
.offset((page - 1) * page_size)
)
posts = session.exec(statement).all()
posts = Posts(__root__=posts)
if isinstance(current_user, RedirectResponse):
is_logged_in = False
else:
is_logged_in = True
if hx_request and page == 1 and len(posts.__root__) == 0:
return HTMLResponse('<ul id="posts"><li>No posts</li></ul>')
if hx_request and len(posts.__root__) == 0:
return HTMLResponse("")
if not hx_request and len(posts.__root__) == 0:
return ["no posts"]
if hx_request:
return templates.TemplateResponse(
"posts.html",
{
"request": request,
"config":...
GitHub - kndndrj/nvim-dbee: Interactive database client for neovim
Interactive database client for neovim. Contribute to kndndrj/nvim-dbee development by creating an account on GitHub.
GitHub · github.com [1]
A neovim database client that I need to check out.
Note
This post is a thought [2]. It’s a short note that I make
about someone else’s content online #thoughts
References:
[1]: https://github.com/kndndrj/nvim-dbee
[2]: /thoughts/
Textual - Using Rich Inspect to interrogate Python objects
Textual is a TUI framework for Python, inspired by modern web development.
Textual Documentation · textual.textualize.io [1]
I love rich inspect. It’s one of my most often used features of rich. It gives you a great human readable insight into python object instances.
>>> from rich import inspect
>>> text_file = open("foo.txt", "w")
>>> inspect(text_file)
I have a pyflyby entry for it so that I can just run it ang get automatic imports. To not clash with the standard library inspect, which is quite useful on it’s own, I have aliased it to rinspect.
from rich import inspect as rinspect
Note
This post is a thought [2]. It’s a short note that I make
about someone else’s content online #thoughts
References:
[1]: https://textual.textualize.io/blog/2023/07/27/using-rich-inspect-to-interrogate-python-objects/
[2]: /thoughts/
External Link
stackoverflow.com [1]
In flask apps I often get a 404 for routes with a trailing slash. This Stack Overflow post shows how to configure flask to allow trailing slashes on some or all routes.
Note
This post is a thought [2]. It’s a short note that I make
about someone else’s content online #thoughts
References:
[1]: https://stackoverflow.com/questions/33241050/trailing-slash-triggers-404-in-flask-path-rule
[2]: /thoughts/
External Link
htmx.org [1]
json-enc extension converts url encoded form values into json encoded data, this is very useful for fastapi [2] to have the same interface for htmx [3] and curl type of interfaces.
Note
This post is a thought [4]. It’s a short note that I make
about someone else’s content online #thoughts
References:
[1]: https://htmx.org/extensions/json-enc/
[2]: /fastapi/
[3]: /htmx/
[4]: /thoughts/
Header Parameters - FastAPI
FastAPI framework, high performance, easy to learn, fast to code, ready for production
fastapi.tiangolo.com [1]
Getting request headers in fastapi [2] has a pretty nice stetup, it allows you to get headers values as function arguments,
I was able to use headers to detect if a request was made from htmx [3] or not.
If the request was made from htmx, then we want a html [4] format, otherwise I’m probably hitting the api programatically from something like curl or python
@post_router.post("/post/")
async def post_post(
request: Request,
post: PostCreate,
current_user: Annotated[User, Depends(try_get_current_active_user)],
session: Session = Depends(get_session),
is_hx_request: Annotated[str | None, Header()] = None,
) -> PostRead:
"create a post"
print('hx_request', hx_request)
db_post = Post.from_orm(post)
session.add(db_post)
session.commit()
session.refresh(db_post)
if is_hx_request:
return templates.TemplateResponse("post_item.html", {"request": request, "config": config, "post": db_post})
return db_post
Note
This post is a thought [5]. It’s a short note that I make
about someone else’s content online #thoughts
References:
[1]:...
GitHub - 1j01/textual-paint: :art: MS Paint in your terminal.
:art: MS Paint in your terminal. Contribute to 1j01/textual-paint development by creating an account on GitHub.
GitHub · github.com [1]
1j01 [2] created a complete working clone of ms paint in the terminal using the textual framework. It’s incredible.
Note
This post is a thought [3]. It’s a short note that I make
about someone else’s content online #thoughts
References:
[1]: https://github.com/1j01/textual-paint
[2]: https://github.com/1j01
[3]: /thoughts/
sqlite-utils now supports plugins
sqlite-utils 3.34 is out with a major new feature: support for plugins. sqlite-utils is my combination Python library and command-line tool for manipulating SQLite databases. It recently celebrated...
Simon Willison’s Weblog · simonwillison.net [1]
As the title states sqlite-utils now supports plugins. I dug in just a bit and Simon implemented this completely with entrypoints, no framework or library at all.
Note
This post is a thought [2]. It’s a short note that I make
about someone else’s content online #thoughts
References:
[1]: https://simonwillison.net/2023/Jul/24/sqlite-utils-plugins/
[2]: /thoughts/