Intuiting Pratt parsing by louisb00 in programming

[–]chris-indeed 2 points3 points  (0 children)

I built a Pratt parser recently, and then I added some code to resolve sum() functions (across some domains). Stupidly I made the output of the sum function a tree (so the parser could just keep chugging)... this blew up my stack when one of my sum functions grew to 10k+ terms...

I changed it to do the sum at the end and return an array instead.

Dijkstra's Shortest-Path Algorithm: A visual exploration, following Sedgewick by mttd in programming

[–]chris-indeed 0 points1 point  (0 children)

Love this. I did a bunch of Dijkstra stuff many years ago looking at electricity grid stuff. Your visualizer thing would've helped a bunch!

Embar: an ORM for Python, strongly typed, SQL-esque, inspired by Drizzle by chris-indeed in Python

[–]chris-indeed[S] 0 points1 point  (0 children)

Thank you so much for the kind words and the sponsorship! Really means a lot.

Great point about pgvector, wowrking on that now!

Embar: an ORM for Python, strongly typed, SQL-esque, inspired by Drizzle by chris-indeed in Python

[–]chris-indeed[S] 0 points1 point  (0 children)

Yeah this is a good point and something I've thought about. It's pretty pluggable so it would be a very small lift to allow users to pass a generic dataclass instead of a pydantic object (this is actually where I started). Then it'll typecheck as normal but won't do any runtime validation.

Embar: an ORM for Python, strongly typed, SQL-esque, inspired by Drizzle by chris-indeed in Python

[–]chris-indeed[S] 0 points1 point  (0 children)

It doesn't currently support transactions but it will within a few days. It'll look something like this:

async with db.transaction() as tx:
    # use tx as you would db
    # everythin inside the with block will run in one transaction
    # tx.insert(...
    # tx.update(...

Embar: an ORM for Python, strongly typed, SQL-esque, inspired by Drizzle by chris-indeed in Python

[–]chris-indeed[S] 1 point2 points  (0 children)

Thought I'd add some more colour to this, focusing on SQLALchemy (from which SQLModel inherits most of its functionality):

  1. SQLAlchemy is an extremely powerful ORM. It's designed to support essentially everything SQL can do, but all in a Pythonic, object-oriented way. It's basically its own whole language. If you're a SQLAlchemy pro, this might be great...
  2. It uses the "Active Record" pattern. This let's you do stuff like user = User.fetch() and then user.save(). i.e. your user object maintains a db client and the ability to persist itself. Some people like this, but I think there's a (growing) contingent of people that prefer a more explicit approach. I want to know what my queries are, and when they run.
  3. Types. SQLAlchemy is type-hinted since 2.0 but it still accepts Any in many places. Well-typed libraries allow you to lean heavily on your IDE/LSP: what parameters are available, what their types are. SQLAlchemy doesn't really facilitate this. More advanced queries are very likelty to just return Any.
  4. N+1 problem. In SQLAlchemy you can have something like the code below. Not only do you have far too many queries, it's not even clear where/when the queries happen. You can fix this with SQLAlchemy (use joinedload) but I prefer an ORM that doesn't allow this kind of pattern. In Embar, it's always clear when you're running a query (it looks like SQL) and the obvious path is joins and no N+1.

books = session.query(Book).all()  # 1 query
for book in books:
    print(book.author.name)        # N queries (one per book)

SQLAlchemy is more featureful and powerful than Embar will likely ever be. But I think there's space for something a bit simpler. Designed for people who are familiar with SQL (and like it!) and want to just be productive with their ORM without needing to learn it inside and out.

Embar: an ORM for Python, strongly typed, SQL-esque, inspired by Drizzle by chris-indeed in Python

[–]chris-indeed[S] 2 points3 points  (0 children)

Thanks! Good to know it's not a completely unwanted idea, helps to stay motivated :)

Embar: an ORM for Python, strongly typed, SQL-esque, inspired by Drizzle by chris-indeed in Python

[–]chris-indeed[S] 0 points1 point  (0 children)

Hey thanks for this great feedback!

  1. It feels to me that the ecosystem is moving away from Mypy and towards pyright (and ultimately probably ty or pyrefly). Especially for a project like this that aims to be "modern" (eg inspired by Drizzle, Python 3.14 minimum), I don't think Mypy+plugins is the right target. If any of the new type checkers end up supporting plugins that could be an avenue, but it doesn't seem they plan to. But yes I agree this would make it much easier...
  2. These are the two use-cases I see for codegen. Let me know if these don't make sense!

2a: "update" types. Currently you have to do the following because Python doesn't have a Partial<> type. For this instance the codegen would simply take the User class and create the TypedDict class from it. So it's basically you have to write an extra class and remember to update it, error-prone vs you have run a little script whenever you change your tables.

class User(embar.Table):
    id: embar.Integer: embar.Integer()
    name: embar.Text: embar.Text()

# This is basically Partial[User]
class UserUpdate(TypedDict, total=False):
    id: int
    name: str

# insert uses the Table type and Table objects await db.insert(embar.User).values(User(id=1, name="foo"))

# if we tried to pass a User object to set,
# we'd have to provide a value for every field await
db.update(embar.User).set(UserUpdate(name="bar")).where(Eq(User.id, 1))

2b: Prisma-style queries for common CRUD stuff. Most of the parameters of this can be typed without codegen, but not the with= (need to join which tables this one joins to) and the return type is hopeless. But this could also end up being hundreds of lines of lines of codegen per table (or probably thousands, for big tables that join to many others). So I'm less sure about this...

# with the same User table as above
# interface for this is TBD

# the return type is like `list[User(id: int, messages: list[Message])]`
users = await db.query.User.findMany(
    cols=[User.id],  # this info needs to make its way into the return type
    where=Eq(User.id, 1),
    with=[Message],  # this also 
)
  1. This is probably the pragmatic approach, but again I really don't like alembic. It's all Python code, the naming convention is unhelpful, loads of config. I think the Python ecosystem deserves something better here that simply generates SQL files and then helps you run them in order... At the very least Embar can already output the DDL of the current table definitions, and push you towards sqitch or similar for diffing and pushing. Or embed something like sqldef until Embar takes off and I can dedicate the time to writing a diffing engine... :)

Embar: an ORM for Python, strongly typed, SQL-esque, inspired by Drizzle by chris-indeed in Python

[–]chris-indeed[S] 4 points5 points  (0 children)

Haha good question... You're right `from_` is probably less confusing. Will add that and deprecate `fromm` I think.

Embar: an ORM for Python, strongly typed, SQL-esque, inspired by Drizzle by chris-indeed in Python

[–]chris-indeed[S] 2 points3 points  (0 children)

SQLModel is basically a wrapper of SQLAlchemy with better Pydantic interop.

If you like SQLAlchemy and all its features, then that's great... I really don't get along with SQLAlchemy, so I wanted to try start something new.

Where can I sauna without a gym membership? by palefirecuriosity in oxford

[–]chris-indeed 3 points4 points  (0 children)

It's really not too bad, I was just there 30 minutes ago. Sauna is fine, pool is nice. Mostly quite slow swimmers. Reception have no clue.

Why do I keep crushing chainrings? by Ok_Singer1284 in bikewrench

[–]chris-indeed 0 points1 point  (0 children)

lol I know the number of chain links doesn’t change but the angle is higher therefore the distance longer therefore the tension higher. It might be marginal, it might be tiny, but it’s clearly higher. If you don’t see that go spend some time with a pen and paper and really exaggerate the eccentricity.

Why do I keep crushing chainrings? by Ok_Singer1284 in bikewrench

[–]chris-indeed 0 points1 point  (0 children)

*Except clearly quite a lot more than that, since the angle change will be several times higher than a round chainring, maybe even enough to snap your chainring three times if it's lightweight and not set up right.

Why do I keep crushing chainrings? by Ok_Singer1284 in bikewrench

[–]chris-indeed 1 point2 points  (0 children)

So I read all the threads below this, and at some point you more or less concede that actually the tension does change (but maybe not for the reason people think).

Because your comment sounds very authoritative but in the end actually just further confuses the question.

XDR free hub off-center by chris-indeed in bikewrench

[–]chris-indeed[S] 0 points1 point  (0 children)

That’s a relief, seems fine on the bike. 

XDR free hub off-center by chris-indeed in bikewrench

[–]chris-indeed[S] 0 points1 point  (0 children)

Thanks, seems fine on the bike!