Package: wintry
❄️🐧A modern python web framework🐧❄️!!! Note
FRAMEWORK'S API IS CONSTANTLY EVOLVING. I PLAN TO GIVE A STABLE VERSION WITH THE 1.0.0
RELEASE. BUT GIVE IT A TRY, IT MIGHT BECOME A GOOD FRIEND OF YOURS :)Hello, friend, welcome to 🐧Wintry🐧. You may have stumble with this project searching
for a python web framework, well, you got what you want.Pherhaps you know many other frameworks, pherhaps you know Django, or maybe Flask,
or hopefully FastAPI. And odds are that you are willing to take a new project for a
ride with a new alternative. Well, 🐧Wintry🐧 is just that, your new alternative, one that
do not push you out of your comfort zone, one that do not get in your away, no matter how
much you scale.Beign accured, if you have used FastAPI, you would feel at home, 🐧Wintry🐧 is heavilly
inspired in FastAPI, it actually uses it whenever it can. But it adds a bunch of
😎'cool'🆒 stuff on top.InspirationsI have used FastAPI a lot for the last year, and I am absolutely fascinated about it.
Speed + Python on the same sentence, that's something to really appreciate. I know, a big
thanks tostarletteproject which is the real hero on
that movie, but, FastAPI adds a ton
of cool features on top: if I would describe them in three words, it would be: Pydantic
and Dependency Injection.On the other hand, we haveDjango, a full-featured Framework,
which has an implementation for nearly everything you could imagine. But it is really opinionated
about how to do stuff. When a project starts to get big, usually developers found themselves
fighting the framework, instead of using it, just because they were trying to apply patterns and
techniques for which the framework was not designed for. FastAPI is like the sweet spot here,
because it offers just enough to get you started fast, and then you can use a vast ecosystem
to flesh it as your project gets big. And that's the problem. Too many tools, too many ways
they can be combined, to many relations that can be configured, and can potentially go wrong.Is cool when you can build a rest api with 10 lines of code. Is cool when you see Pydantic used
in such a clever way to achieve model-binding at request time. Is cool that FastAPI gives us
already scoped (Request based) dependency injection. But if we are coming from .NET, or Spring,
or we are really committed to Microservices or Fully Decoupled Monolith with advanced techniques
such as CQRS and DDD, we start missing some good old controllers, some good old Constructor
based IoC. Also, it would be nice to have some form of Repository, right? Maybe some support for
Command and Query separation (And Events🤞). What about atomic transactions, can we have that too ?
And please, support for SQL and NoSQL DB 😊, because that's trending, and I want to split
my data into Write models and read models. Speaking of which, allow me to easily configure more
than one DB right. And please, I love the ORM and change tracking functionalities of SQLAlchemy,
can we have that for all our DB and integrated with the atomic transactions ? And can we .....Yeah, a lot of good features, and all of them fully compatible with each other. That's what
🐧Wintry🐧 is all about. Be opinionated about some conventions and provide a large
range of tools, fully compatible with each other, that allows you, the developer, to
"DESIGN" and "IMPLEMENT" your system in the way you want, without the framework interposing
in your way, with the performance of the latest technologies, with the language that we all
love 😊.Sounds good right ? Lets see how it looks likefromwintryimportscoped,controller,get,post,App,AppBuilderfromwintry.sqlimportFieldfrompydanticimportBaseModelfromsqlmodelimportSQLModelclassHero(SQLModel,table=True):id:str|None=Field(primary_key=True,default_factory=lambda:str(ObjectId()))name:strcity:str|None=NoneclassCreateHeroModel(BaseModel):name:strcity:str|None=None@scopedclassHeroRepo:def__init__(self):pass@controllerclassHeroesController(object):heroes:HeroRepo@get("/",response_model=list[Hero])asyncdefget_heroes(self):returnawaitself.heroes.find()@post("/")@atomic(with_context=AppDbContext)asyncdefcreate_hero(self,create_hero_model:CreateHeroModel):new_hero=Hero.from_orm(create_hero_model)awaitself.heroes.add(new_hero)return"Ok"app=App()AppBuilder.use_sql_context(app,SQLEngineContext,"sqlite+aiosqlite:///:memory:")Cool ehh !?. Right now, you would have a fully-featured app. It is not your typical "hello world" app,
because that's boring. Instead, you now have an api, that creates and list heroes, with a clear separation
of your models, data access, and a presentation layer in the form of controllers. A paradise for testing and
decoupling. Furthermore, you have the@atomicdecorator, which will roll-back your db changes
whenever an error occurs withing the post method. You have, as expected from FastAPI, a fully documented
API, with type annotations everywhere. Even more, you have a repository automatically created for you,
and the controller already depends on an abstraction, not the concrete repo, so it could easily be changed
for a mock for testing or whatever you want. Almost forgot, you can exchange the DB provider for any async
backend, sqlalchemy will handle it for you 😉.Umm, but I want to use MongoDB, now, can I do it. YES!! Of course you can, and even more,
because you only depend on abstractions to build your controllers, it is now really easy
to swap the implementation for a NoSQL one.First we need to use a different contextfromwintryimportscopedfromwintryimportNosqlAsyncSessionfromwintryimportDbContextfromwintryimportMotorContext@scopedclassMongoContext(NosqlAsyncSession,DbContext):def__init__(self):super().__init__(MotorContext.get_client(),database="test")asyncdefdispose(self):passThen we register the new repository and remove the SQLModel from our modelfromwintryimportNoSQLModelfromwintryimportNoSQLRepositoryfromodmantic.bsonimportObjectIdclassHero(NoSQLModel):id:str=Field(primary_field=True,default_factory=lambda:str(ObjectId()))name:strcity:str|None=None@scoped(of=AbstractRepository[Hero,str])classHeroRepo(NoSQLRepository):def__init__(self,context:MongoContext):super().__init__(context,Hero)And finally we register our new backendAppBuilder.use_mongo_context(app,MotorContext,"mongodb://localhost:27017")That's it. Our controller still will be working the same.Side Note !!: If you run your application with:$ gunicorn -w 4 -k uvicorn.workers.UvicornWorker main:appYou will see a surprise. Usually, when using MongoDb with Motor for the async Backend, and run
the app with gunicorn, because of the global client, the app will crash, with a Detached Task error.
You might be surprised that wintry handles this case for you, be defining the client inside a
Context var on the MotorContext, and then, retrieving it on each request when instantiating
the MongoContext, that's whatMotorContext.get_client()is intended for.If you are diving into Clean Architecture, DDD, and some advance stuff, you might be
unconformable by changing your model, after all, my hero should be just fine with its
new data store, it should not even care about ir right !?.Well, that's pretty easy to achieve using SQLAlchemy (and soon with NoSQL too, thanks to
the incoming mapping layer for nosql-wintry). We can use imperative mapping as describedhere,
and now our Hero will be a pure Python Object, and our repos will still behave the same,
and you can go and tweak your DB as much as you need and maintain our domain models as
clean as possible.So far so good. At this point, wintry will prove to be really helpful, but it is not
bringing too much new to the table right ? I mean, yeah, you got repositories, decoupling
with abstractions, some fancy config API for setting up new DB Contexts as we can do
in .NET, we have Dependency Injection .... , wait, I said Dependency Injection, do I said
fully compatible with FASTAPI 😱😱!!!???If you have used FastAPI before, you may have noticed that Dependencies, are not truly
Dependency Injection, is more like a Request Bound Resolution method, which is really cool
and helpful, but at the same time, tides you to the controller level. Furthermore, if I want
to use the same dependency in a bunch of methods, I will have to either replicate that
dependency on each method signature, or rescind from its return value. Imagine that you
want to secure now your two endpoints, using the same approach as the security section
in the FastAPI tutorial. In order to access the user information inside the endpoint,
you will have to declare something like this@app.get("/")asyncdefmy_awsome_endpoint(user:Depends(get_logged_user)):...Not cool. I want to secure all my endpoints inside a controller and I don't want to
repeat my self. FastAPI with a penguin to the rescue, inside controllers, you can use
your FastAPI dependencies as usual, BUT, you can access their values, like this:@controllerclassMyAwsomeController:user:LoggedUser=Depends(get_logged_user)@get("/")asyncdefsecured_endpoint(self):returnself.userBut wait, there is a lot more, the@scopeddecorator
allows the repository to be injected inside the controller
constructor, just like happens in .NET Core or Java Spring, and combine
it with the FastAPI Dependency Injection for giving you, the developer,
an extremely powerful tool to extend, reuse and configure your entire
application (tip: Most of the FastAPI ecosystem is build around Dependency Injection).
In fact, wintry relies heavily on the IoC (Inversion of Control) module, which, is
I like to call the Igloo.Non-Fastapi DI is configured using two decorators:@scopedand@provider, which
gives you the ability to create Scoped (request bound), Transient (on demand) and Singleton
instances for each declared dependency. To prepare an object for injection, you could
use the@injectdecorator.@scopedandprovidermark their classes or functions
for injection as well. Other decorators already prepare their targets for injection, as@controllerand@microserviceInstallationAs simple as use$ pip install wintryor with poetry$ poetry add wintryFeaturesThere is a lot more to know about Wintry:Dependency Injection (Next Level).Publisher Subscribers.Services.Short: Focus on what really matters, write less code, get more results.Everything from FastAPI in a really confortable waySettings based on Pydantic.A handy cli for managing projects (Feeling jealous of Rails ?? Not any more): Work in progress.ROADMAPPerformance similar to FastAPI (When possible, actually FastAPI is a LOWER BOUND) (need benchmarks and identify bottle necks).Create documentationAdd more features to the feature list with links to
the corresponding documentationAdd RPC support (Maybe protobuf, raw TCP, Redis, RabbitMQ, Kafka, etc)Ease registration of MiddlewaresProvide Implementation of Authorization ServicesCreate CLI for managing projectTemplatesMaybe some ViewEngine (Most likely will be based on Jinja2)ContributionsEvery single contribution is very appreciated. From ideas, issues,
PR, criticism, anything you can imagine.If you are willing to provide a PR for a feature, just try to
give at least some tests for the feature, I do my best
mantaining a pool of tests that will be growing with timeIssue TrackerFork the repo, change it, and make a PRThanksTo @tiangolo for the amazingSQLModelandFastAPITo the amazingDjango TeamTo the Spring Project andNestJSfor such amazing frameworksLicenseThis project is licensed under the MIT License