Replies: 4 comments 6 replies
-
@jweckman let me give you some good news. We are more than happy to have support for EdgeDB. I've tried myself and I can't deny my excitement as I do also believe the future of DBs as we know it will pass through this revolutionary technology. We do appreciate you excitement and words and we hope you enjoy Esmerald as this was always the purpose. The modularity and being like that and supporting msgspec helps you a lot 🙂. In terms of contributions you are more than welcomed to add what you think it would be a amazing feature to Esmerald. In any way you see fit. You can even create a plugin for it https://esmerald.dev/pluggables The world is your oyster. I myself have been thinking how we could bring EdgeDB into Esmerald world and give it to the people to actually use both in a clean manner. Does this answer your question? 🙂 Having a pluggable would be amazing since you decouple the core of Esmerald and you bring EdgeDB into the world of what we want. https://esmerald.dev/pluggables See if this make sense to you? Feel free to suggest, play and let's contribute and develop this idea. I do really like it and I'm sure the community will appreciate your contributions and excitement. P.S.: I think you will like the approach of the pluggable because this will not only allow you to give you the freedom to play around with this amazing functionality but the integration with Esmerald is.... Modular 🙂 See if this and the docs make sense or if you are thinking of something else. I do love your idea a lot. |
Beta Was this translation helpful? Give feedback.
-
@jweckman i also do like your approach of msgspec with EdgeDB. Basically when the msgspec models are generated you can use them directly with Esmerald as payload in the APIs and delegate validations to msgspec itself. Since Esmerald supports msgspec, that is a massive win and since EdgeDB handles with migrations and whatnot, you have the perfect combination there. Fantastic would be, as suggested, having this as pluggable for Esmerald. I don't want to overstep your toes with this and I do believe you could bring invaluable contributions if your were interested in this. This is amazing, really. Another way, if it makes it easy, it's to then write an application with Esmerald and EdgeDB, the bellow world and we could perhaps take it from there? Maybe it can be easier for you? Assembling the app should be ok and then we extract what we want and need and add it into Esmerald? Or you can go through the pluggable route where basically you are the boss and owner of this amazing idea and of course we add you into the documentation and references to that same project/pluggable? It's up to you. Let me know your thoughts on this |
Beta Was this translation helpful? Give feedback.
-
Wonderful! Without having any experience with Esmerald i first got the single file hello-world example running and extended it to clarify that my ideas actually work. Since i'm more familiar with FastAPI i checked that using the same concepts as in the code below actually works there with msgspec imported, which is already a very good sign. Below is a semi-pseudocode chronological example that explains how data comes in from edgedb and makes its way out of an example endpoint: import msgspec
import edgedb
from esmerald import Esmerald, Gateway, JSONResponse, Request, get, status
import uuid
from datetime import datetime
# EDGEDB CLIENT SETUP
def get_edgedb_client(request: Request) -> edgedb.AsyncIOClient:
return request.app.state.edgedb
# EDGEQL TABLE/MODEL EQUIVALENT (NOT PYTHON - THEREFORE COMMENTED OUT)
# type Payment {
# annotation description := "Payment";
# required date: datetime {
# default := datetime_current();
# };
# partner_name: str {
# constraint max_len_value(50);
# constraint exclusive;
# };
# required amount: float64 {
# };
# info: str {
# constraint max_len_value(1000);
# };
# }
# AUTO-GENERATED EDGEDB PYTHON QUERY MODEL BASED ON PREVIOUS EDGEQL TABLE/MODEL
import dataclasses
@dataclasses.dataclass
class GetPaymentResult():
id: uuid.UUID
date: datetime
partner_name: str
amount: float
info: str | None
# AUTO-GENERATED EDGEDB PYTHON METHOD THAT WE USE TO FETCH DATA
async def get_payments(
executor: edgedb.AsyncIOExecutor,
# No arguments for simplicity
) -> list[GetPaymentResult]:
return await executor.query() # Contains generated EDGEQL query as string in the real world
# PAYMENTSTRUCT BELOW IS WHAT WE WANT TO AUTO-GENERATE, PERHAPS IN DIFFERENT WAYS. LOOKS LIKE THEY SUPPORT METHODS AS WELL
class PaymentStruct(msgspec.Struct):
id: uuid.UUID
date: datetime
partner_name: str
amount: float
info: str | None
def get_repr(self):
return f"{self.date}: {self.partner_text} | {self.amount}" # Just something to demo that we can add methods
@get()
async def payments_endpoint(request: Request) -> dict:
# For really light weight immediate forwarding or pure data-based parsing, we can use
# the payments as-is or directly serialize them using msgspec
payments = await get_payments(
edgedb.AsyncIOExecutor,
)
# In case we need logic, we want to be able to convert the edgedb.object records into something
# that allows methods and serialization. msgspec.struct should be a perfect fit!
payment_structs = [
msgspec.convert(
payment,
PaymentStruct,
from_attributes=True
) for payment in payments
]
payments_encoded = [msgspec.json.encode(payment_struct) for payment_struct in payment_structs]
# Return the results in whatever shape Esmerald expect, not part of the problem
return payments_decoded
@get(status_code=status.HTTP_200_OK)
async def home() -> JSONResponse:
return JSONResponse({
"detail": "Hello EdgeWorld"
})
app = Esmerald(routes=[
Gateway(handler=home),
Gateway(handler=payments_endpoint),
]) Before we make any decisions regarding where to implement, i would first like to understand the overall architecture. I'm fairly sure that it already would be possible to write production apps based on the example above. I'm also sure that i'm probably doing more than needed by hand and that by leveraging what Esmerald has to offer could cut out some lines from the workflow. My philosophy (and practical experience) is that we want two ways to process the data that comes from our DB:
If i was to solve my own need naively right now with minimal effort, i would probably look into just extending the edgedb-py repo code to also generate the PaymentStruct for us. Given that they are almost identical i don't think this should take more than a couple of hours to figure out. What i'm most concerned about is whether this mapping makes any sense when we build a complex query that references 5 different objects in the DB and spits out --something-- that i can't even imagine right now. In theory it should always work because whatever it spits out the auto-generated python mapping should be able to map all our classes, dicts, lists or whatever comes out - no matter how nested. It's obvious from my example above that this should work fine when doing SQL style queries on a single "table" but i don't think auto-mapping would make sens if this falls apart as soon as people start "joining" "tables" in edgeql. It would be very helpful if you could have a look at this: and then post a modified pseudo-code example (use my stuff above) of how you think this should be done in Esmerald. From what i've seen i agree that a pluggable probably is the correct way to go but for now i just want verification on the parts that should be involved in the flow. Looks very exciting already :) |
Beta Was this translation helpful? Give feedback.
-
@jweckman as far as I could see, we can separate the logic of the payments inside a DAO or in this case an AsyncDAO and what I think you also want (and I agree) is an abstraction to work directly with Esmerald since its 100% compatible with Esmerald in the format of an ODM. Like this one but for EdgeDB. Then, because the ODM would be msgspec oriented (and not pydantic as the example in the URL), this would integrate seamlessly with Esmerald as model and validator and the queries would run automatically through it. Is this correct? What I'm failing to see is why not using EdgeDB ORM directly since they provide basically everything you need? What you did was not too far from what you can do with Esmerald, an example would also be import dataclasses
import uuid
from datetime import datetime
from typing import List
import edgedb
import msgspec
from esmerald import Esmerald, Gateway, JSONResponse, Request, get
# EDGEDB CLIENT SETUP
def get_edgedb_client(request: Request) -> edgedb.AsyncIOClient:
return request.app.state.edgedb
# EDGEQL TABLE/MODEL EQUIVALENT (NOT PYTHON - THEREFORE COMMENTED OUT)
# type Payment {
# annotation description := "Payment";
# required date: datetime {
# default := datetime_current();
# };
# partner_name: str {
# constraint max_len_value(50);
# constraint exclusive;
# };
# required amount: float64 {
# };
# info: str {
# constraint max_len_value(1000);
# };
# }
# AUTO-GENERATED EDGEDB PYTHON QUERY MODEL BASED ON PREVIOUS EDGEQL TABLE/MODEL
@dataclasses.dataclass
class GetPaymentResult:
id: uuid.UUID
date: datetime
partner_name: str
amount: float
info: str | None
# AUTO-GENERATED EDGEDB PYTHON METHOD THAT WE USE TO FETCH DATA
async def get_payments(
executor: edgedb.AsyncIOExecutor, # No arguments for simplicity
) -> list[GetPaymentResult]:
return await executor.query() # Contains generated EDGEQL query as string in the real world
# PAYMENTSTRUCT BELOW IS WHAT WE WANT TO AUTO-GENERATE, PERHAPS IN DIFFERENT WAYS. LOOKS LIKE THEY SUPPORT METHODS AS WELL
class PaymentStruct(msgspec.Struct):
id: uuid.UUID
date: datetime
partner_name: str
amount: float
info: str | None
def get_repr(self):
return f"{self.date}: {self.partner_text} | {self.amount}" # Just something to demo that we can add methods
@get()
async def payments_endpoint(request: Request) -> List[PaymentStruct]:
# For really light weight immediate forwarding or pure data-based parsing, we can use
# the payments as-is or directly serialize them using msgspec
payments = await get_payments(
edgedb.AsyncIOExecutor,
)
# In case we need logic, we want to be able to convert the edgedb.object records into something
# that allows methods and serialization. msgspec.struct should be a perfect fit!
payment_structs = [
msgspec.convert(payment, PaymentStruct, from_attributes=True) for payment in payments
]
[msgspec.json.encode(payment_struct) for payment_struct in payment_structs]
# Return the results in whatever shape Esmerald expect, not part of the problem
return payments_decoded
@get() # defaults to 200 (status code)
async def home() -> JSONResponse:
return JSONResponse({"detail": "Hello EdgeWorld"})
app = Esmerald(
routes=[
Gateway(handler=home),
Gateway(handler=payments_endpoint),
]
) |
Beta Was this translation helpful? Give feedback.
-
Having tried out EdgeDB and their (IMO potentially revolutionary) EdgeQL, it would seem like it could work very well with this framework after implementing a simple EdgeQL ODM (maybe?). Do you think you could read this first:
https://jcristharif.com/msgspec/examples/edgedb.html
To sum it up, using msgspec we can already map EdgeDB objects to msgspec Structs as follows:
EdgeDB Schema:
msgspec.Struct types mirroring Schema above:
After this manual mapping we can already get to a point where we can serialize msgspec structs:
Having come this far are we done? To me it seems like we would want to auto-generate the msgspec.Struct classes from this already auto-generated dataclass by EdgeDB (Person as example since simpler):
Edgedb has a separate repo edgedb-py that auto-generates python code from the EdgeDB queries. To me it seems that it should be trivial to extend the EdgeDB generation code to also create msgspec.Structs.
Given your expertise and your seemingly modular approach and focus on simplicity, could you advise me on whether this makes any sense or if i should use an even lighter framework than yours? Let's just say i would be extremely willing to make some contributions myself if my suggestions here look like they could work.
I have difficulties to contain my excitement because it seems to me like i have discovered a solo developers most powerful stack for making small business applications:
EdgeDB: Database with schema,data, automated db migrations, and a lot more validation than plain old postgres. You get the performance of postgres while getting rid of BOTH the ORM AND SQL (both of which have painful tradeoffs)
Esmerald + Jinja2: What looks to me like the more modular successor to FastAPI with support for the faster msgspec library that incidentally has very good EdgeDB support
HTMX + Tailwind CSS: Get rid of javascript and have almost all of your logic in python with a little bit of light client side js scripting without the horrible garbage called node.js and its hundreds of megabytes of dependencies.
Any commentary on feasibility/design is much welcome. If someone can validate my ideas i would be happy to publish a hello-world template and write EdgeDB/Esmerald binding code under this project.
Beta Was this translation helpful? Give feedback.
All reactions