This was my go to for learning Flask, I wish I never found it. Flask has a very niche use case in my opinion and if you need something to scale (in terms of code complexity) Flask is the absolute worst decision you can make.
Try Django instead. If you read my comment history you'll see that I learnt this the hard way.
In my experience it's quite the opposite. We use Flask for a large-scale webapp in production quite successfully and it's enabled us to reduce application complexity significantly.
While you get up and running faster with Django, in the long run you spend more time fighting it, whereas with Flask you only spend time on stuff that you actually need.
Yep. This is exactly how I feel. Django is great to get projects going, but eventually you just end up fighting it a lot.
I even recommend people not to install too many flask extensions, just use Django if that is how you like your development. Flask is much better when you just build for it - It takes longer to deliver, but you'll be enjoying that project for years.
Different strokes for different blokes I guess. For me, Django has always been a massive pain to get up and running with, but once the groundworks done it just gets easier.
This is inspired by Elixir's compile-time config per environment/release/etc...
Instead of a huge settings.py, I have:
settings/__init__.py : Import the correct settings
settings/core.py : The stuff that Django generates and that you touch almost never
settings/base.py : The basic settings of your project (your apps, middlewares, ...)
settings/envs/dev.py : Development settings
settings/envs/prod.py : Production settings (read from env vars, the 12 factors app way)
settings/envs/test.py : Test suite settings
2. Separate your business code from your Django apps
In most project, I have the package `myproject.lib` which contains all the business code, easily testable and mockable. Then I have the Django apps in `myproject.apps.<appname>` which imports from `myproject.lib`.
This allows you to make your views quite small because all the logic is not there.
3. Most of the time, you don't need class-based views.
A small function which call your business code and render a JSON document, or an HTML document is more than enough. Django provides many function decorators that are easier to reason about (login_required, permission_required, require_http_methods, ...).
4. You might not need an API
Server Side Rendering is coming back into trends, huge SPA that are heavy on the client's resource are becoming less frequent. Then, why would you need a REST/GraphQL API?
You can work with HTMX, Django Forms, small views that call your business code and return some small rendered template, then sprinkle some Alpine.JS (or jQuery) on top of it, and you get your SSR SPA that scales well enough.
5. Deploy with gunicorn and whitenoise
By default, Django won't serve staticfiles in production, you need to distribute them via a CDN. Which is a bit more complicated to deploy. Instead, use Whitenoise to force Django to serve staticfiles but with the correct HTTP cache headers so services like Cloudflare can take over after the first request (usually done by your E2E test suite by the way).
Then you only need one Docker image, one gunicorn, and if you deploy in Kubernetes, one Ingress resource.
6. Use django-anymail
This is, by far, the best library out there. I usually make a mailjet account, add my API key to my settings, and use the mailjet backend of django-anymail. No SMTP setup required.
All of those tips might seem obvious, but they get you far, very far. Also, IMHO the urls.py from Django is vastly superior to the route decorator from Flask.
To me its not the app settings or structure but the view/viewset/mixin and plugins interactions that are black holes. I only skimmed the docs but I don't know who does what and when. Everyday I scream "magic" .. probably more than a rails guy.
Like I said, you don't need class-based views if you split your code correctly.
You have a function, which takes a request and URL parameters, and returns an HTTP response. You add some decorators to require an authenticated user and/or some permissions. That's it.
This is no different than flask views.
Mixins are an OOP pattern that promotes spaghetti code if not done carefully, especially in Python where there is no encapsulation.
ViewSets are from the Django REST Framework, this is simply all the boilerplate code that you copy/paste hundreds of times. And even that, you might not need it if your API is different enough from your database schema. Even more if you don't need an API.
There is nothing "magical", every single framework have a happy path, you should follow it if you decide to use this framework.
And no, Flask is not a framework, it is a library, much smaller scope, much more boilerplate code that you need to copy/paste, or you build your own framework on top of it.
Flask is about the freedom of choice while Django made the decisions for you 10 years ago. Having some experience with Rails, SpringBoot, Phoenix, Symphony, Laravel, Flask, Express, etc... it turns out that I don't want to make those decisions every single time, for every single project.
Which is why I put all the logic into a single place.
Extra methods on Django Models? Nope. A function in my business logic package which takes a model instance as parameter? Yes. Models should only contain data.
Huge views that do more than one thing? Nope. A function, or a class, in my business logic package which takes the request data (POST form, GET query string) and returns the data to feed to the template and/or the "state" needed to decide which template to render? Yes. Views should be small.
Also, split your templates, don't be afraid of `{% include %}` (which works well with HTMX as well).
This methodology also have another huge advantage. If you need to walk away from Django, you can. Your business logic won't change that much (there are not many difference between a Django Model instance and an SQLAlchemy model instance, or even a MongoEngine model instance). Your business logic knows nothing of HTTP or serialization.
Django, and Flask or any other framework are just the glue between the WSGI/ASGI and your business code.
If your code is async but your web framework isn't, you can still put a `asyncio.run` or `trio.run` inside your view, and you won't have to change a thing once you switch to an async web framework.
Basically, I use Django for:
- the ORM
- the Forms
- the routing with urls.py
- the settings management
- the templating engine
- the builtin User system
- the builtin admin website
- the vast ecosystem (django-anymail, django-tailwind, django-money, django-flags, django-social-auth, ...)
What I dislike about Flask is:
- routing is tied up to the views (via a decorator)
- the request object is a global singleton instead of a function parameter
- no builtin admin website (this is just so useful to get things going at the beginning)
- there is a lot of boilerplate to make the different extensions work together where django is just about adding a string to INSTALLED_APPS
Thanks for you thorough answer, I'll see how it goes for me in the coming months. I may find myself surprisingly happy.
btw, didn't django add routing decorators too ? I always prefered it to split file because .. it's a small amount of information that needs to be tracked separately mentally if in urls.py .. plus inclusion/sub-routes feel confusing (but that may only be me)
When I'm onboarding on a huge code base, someone from the frontend reports a bug, I'm like "ok so where does this URL go?", I open the urls.py (which can be split with include), and from there I navigate to the relevant piece of code quite easily.
But with routes as decorators, you need to know the structure of the code beforehand. Which can take some time if you're onboarding a new project.
IMHO, having the route next to the code managing the request does not add useful information to the code being modified.
Also, thanks to django-flags (a feature flag django extension), I can easily enable/disable routes with feature flags in the urls.py without overwhelming the "request handler" with irrelevant information.
strongly disagree with this. flask is what you make of it. you create your design patterns. for example, here's an MVC boilerplate i use for all my flask apps: https://github.com/esteininger/flask-mvc-boilerplate
After reading your comment history and searching it, it seems to me that you never learned to use Flask. Jumping into new project and learning a framework at the same time means that you may have to restructure everything after you have learned it.
I learnt it quite well actually. Used it in a few projects one quite large.
It worked ok for internal apis not open to customers, but even then Django was much better as just setting up the admin side provided a lot of value to the business (for free).
All forgotten now but wasted a huge amount of time glueing things together that came for free with Django.
Try Django instead. If you read my comment history you'll see that I learnt this the hard way.