Just like any software, Emmett is changing over time, and most of these changes we introduce don't require you to change anything into your application code to profit from a new release.
Sometimes, indeed, there are changes in Emmett source code that do require some changes or there are possibilities for you to improve your own code quality, taking advantage of new features in Emmett.
This section of the documentation reports all the main changes in Emmett from one release to the next and how you can (or you should) update your application's code to have the less painful upgrade experience.
Just as a remind, you can update Emmett using pip:
$ pip install -U emmett
Emmett 2.6 release is focused on modernising part of the codebase. In this release we also rewrote part of the router and the request parsers in Rust, providing additional performance gains to all kind of applications.
This release introduces some minor breaking changes and few deprecations, while introducing some new features.
Prior to Emmett 2.6 the contents of Request.files
were loaded in memory on parsing. This might have led to issues with memory allocations, thus in 2.6 the file contents are spooled to temprorary files on disk.
The main consequence for this change is that code relying on the old behavior is now subject to errors. This only involves code relying on the previous – and undocumented – stream
attribute of the files
object values; all the other interfaces (iteration, save
method, etc.) are still the same.
In case your application code falls into this scope, you should change the involved lines accordingly:
files = await request.files
# prior to 2.6
data = files.myfile.stream.read()
# from 2.6
data = files.myfile.read()
With Emmett 2.6 the default logger configuration will now use the standard output rather than a rotating file handler.
This is considered a minor breaking change, as it involves the default configuration, and thus can be set to the previous one:
app.config.logging.production.file.no = 4
app.config.logging.production.file.max_size = 5 * 1024 * 1024
Stream helpers like stream_file
and stream_dbfile
are now deprecated in favour of the newly introduced response wrap methods.
Code involving this methods like:
from emmett.helpers import stream_dbfile
@app.route("/download/<str:filename>")
async def download(filename):
stream_dbfile(db, filename)
should be converted to the new format:
from emmett import response
@app.route("/download/<str:filename>")
async def download(filename):
return response.wrap_dbfile(db, filename)
iter
, aiter
and http
route outputsEmmett 2.6 also introduces support for Python 3.13.
Emmett 2.5 release is highly focused on the included HTTP server.
This release drops previous 2.x deprecations and support for Python 3.7.
Emmett 2.5 drops its dependency on uvicorn as HTTP server and replaces it with Granian, a modern, Rust based HTTP server.
Granian provides RSGI, an alternative protocol to ASGI implementation, which is now the default protocol used in Emmett 2.5. This should give you roughly 50% more performance out of the box on your application, with no need to change its code.
Emmett 2.5 applications still preserve an ASGI interface, so in case you want to stick with this protocol you can use the --interface
option in the included server or still use Uvicorn with the emmett[uvicorn]
extra notation in your dependencies.
Emmett 2.5 also introduces support for application modules groups and Python 3.11
Emmett 2.4 release is highly focused on the included ORM.
This release doesn't introduce any deprecation or breaking change, but some new features you might be interested into.
extend
and include
blockswatch
parameter to ORM compute decoratorset
command to migrations CLIEmmett 2.4 also introduces support for Python 3.10.
Emmett 2.3 introduces some deprecations and some new features you might be interested into.
Since its birth, Emmett tried to maximise its compatibility across platforms, and consequentially provided the cryptographic functions needed for its security modules in pure Python versions.
Nevertheless, those implemetations clearly provide poor performances, and we opted for rewriting them using Rust producing the emmett-crypto package. This refactoring also gave us the chance to upgrade and modernise the algorithm used to encrypt the session data when using the cookies implementation.
Since the new algorithm is not compatible with the previous one, and the new implementation is an optional extra dependency in Emmett 2.3, we decided to deprecate the existing one letting you to decide when to switch to the new one, as such switch will invalidate all the active sessions.
This is why the SessionManager.cookies
constructor in Emmett 2.3 has a new encryption_mode
argument with default value set to legacy. In order to upgrade to new code, you should change your dependencies file to install Emmett with the crypto extras, like:
pip install -U emmett[crypto]
and change the value for the encryption_mode
parameter to modern:
SessionManager.cookies('myverysecretkey', encryption_mode='modern')
Note: since
emmett-crypto
module is written in Rust language, in case no pre-build wheel is available for your platform, you will need the Rust toolchain on your system to compile it from source.
With version 2.3 we introduced the following new features:
belongs_to
and refers_to
relations--dry-run
option to migrations up
and down
commandsAlso, in Emmett 2.3 we added an optional extra crypto
dependency which re-implements cryptographic functions using Rust programming language.
Emmett 2.2 introduces some changes you should be aware of, and some new features you might be interested into.
Emmett versions prior to 2.2.2
contain a bug in the library responsible of PBKDF2 hashes generation. The produced hashes contain wrong characters due to a wrong decoding format. This means that any stored value produced by the library should be fixed, otherwise further hash comparisons will fail.
Previous generated values look like this:
pbkdf2(2000,20,sha512)$adcc9967adbd17f6$b'c68deb7f0690c2cafb99b1f323313b17117337ac'
while the correct format is:
pbkdf2(2000,20,sha512)$adcc9967adbd17f6$c68deb7f0690c2cafb99b1f323313b17117337ac
In case your application use the Auth system with default configuration, the passwords' hashes stored in the database contain the wrong characters, and you should fix them manually. Here is a code snippet you can use:
with db.connection():
for row in User.where(lambda u: u.password.like("%b'%")).select():
password_components = row.password.split("$")
row.update_record(
password="$".join(password_components[:-1] + [password_components[-1][2:-1]])
)
db.commit()
The following changes are considered minor changes, since they affect only default values, and can be consequentially set to the previous ones:
SameSite
value for sessions cookies is now Lax instead of Noneserve
command is now info in place of warningMind that Emmett 2.2 also drops support for previous 2.x deprecations.
With version 2.2 we introduced the following new features:
Emmett 2.1 introduces some minor breaking changes you should be aware of, and some new features you might been interested into.
The sanitizer module was a very old module included in Emmett since long time ago. This module was written using the formatter
module from standard library, deprecated since Python 3.4, and scheduled for removal in next Python versions.
The only place using this library in Emmett was in html.safe
method with the sanitize
option activated. Considering the lack of usage from the community, the lack of maintenance of the library and in order to avoid preventions in supporting future Python versions, since Emmett 2.1 this library is not available anymore. In case you need a replacement for the sanitizer, you must implement by yourself.
Moreover, calling html.safe
with sanitize
option set will warn about the behaviour difference.
Due to removal of sanitizer
library, html.safe
is now exactly the same of html.asis
. You should switch the usage in your code.
The run
method in App
class is now deprecated. Please rely on develop
and serve
command from CLI.
Prior to Emmett 2.1 extensions signals were implemented as strings. Since 2.1 a new Signals
enum is available in extensions module you should use in place of strings.
With version 2.1 we introduced some new features:
SameSite
parameter support on session cookiesEmmett 2.1 also introduces (beta) support for Python 3.9, and type hints on all major interfaces.
Version 2.0 introduces quite a lot of changes, with the majority of them being breaking. This is due to the drivers of this new major version:
Since these drivers changes completely the approach used to write applications, the framework name and packages were also changed, in order to avoid developers to inadvertently update between versions and brake all of their code. So, goodbye weppy, welcome Emmett.
If you are really considering porting your application to Emmett from weppy, here is the list of steps.
Since the package name is changed to emmett
, you will need to rewrite all the imports in your application from the old ones:
from weppy import App, request
to the new package:
from emmett import App, request
In general all the previous importables described in documentation are unchanged in Emmett, but we dropped quite a lot of the internals part of the WSGI flow, so in case you imported some peculiar resources and your code raises ImportError
, please check the source code for the relevant changes.
Mind also that the weppy
command is now the emmett
command.
Since we dropped the support for Python 2, if your application is written using this version of the Python language, you need to update your code, following one of the available guides out there.
Also mind that the minimum supported Python version in Emmett is 3.7.
In Emmett everything regarding the request flow follows ASGI implementation, and, thus, all the relative code should be asynchronous.
Operative speaking, you can keep your routes as standard methods, but whenever you access Request.body_params
, you need to change your code from synchronous:
@app.route()
def some_route():
value = request.body_params.foo
to asynchronous one:
@app.route()
async def some_route():
value = (await request.body_params).foo
In general, if you need to repeatedly access Request.body_params
, might be a good idea to store it in a variable:
@app.route()
async def some_route():
params = await request.body_params
foo = params.foo
Mind that, since forms process the request's body, they're awaitable objects too:
@app.route()
async def some_route():
form = await SomeModel.form()
Since the pipeline is strictly tied to request flow, all the methods inside Pipe
class are now coroutine functions, so while in weppy you wrote:
class MyPipe(Pipe):
def open(self):
pass
def close(self):
pass
def pipe(self, next_pipe, **kwargs):
return next_pipe(**kwargs)
def on_pipe_success(self):
pass
def on_pipe_failure(self):
pass
in Emmett you need to write:
class MyPipe(Pipe):
async def open(self):
pass
async def close(self):
pass
async def pipe(self, next_pipe, **kwargs):
return await next_pipe(**kwargs)
async def on_pipe_success(self):
pass
async def on_pipe_failure(self):
pass
Due to the new asynchronous flow, we also introduced a big step-forward in the pipeline flow: while in weppy the open
and close
pipes' methods were called one after another, in Emmett they get scheduled in the loop at the same time. This means that the order on which these methods get called is no more predictable.
In general, if you need the execution order of your code to be preserved, use the pipe
method instead of the open
or close
ones.
Emmett 2.0 introduces the files
sdict attribute in the Request
object.
As a direct consequence, your code should be updated to handle uploads from the new attribute, instead of looking for uploads in the body_params
one.
An example might be the following:
@app.route()
async def upload():
files = await request.files
file = files.upload_param
await file.save(f"somepath/{file.filename}")
Since 2.0 Emmett uses Severus as its internationalization engine.
Versions prior to 2.0 implied usage of python files for translations, while in 2.0 supported formats are JSON and YAML.
This means you should convert your Python translations to JSON files, which in the majority of cases it just requires changing the files extensions from .py
to .json
.
Mind that the format syntax also changed: since Emmett 2.0 the only supported syntax for symbols in strings is the format
one. Consequentialy you have to change your translation strings containing old symbols:
{
"you received %(like)s": "hai ricevuto %(like)s"
}
to the braces format:
{
"you received {like}": "hai ricevuto {like}"
}
Here we list other breaking changes introduced with 2.0
Since the introduction of ASGI and asynchronous code we had to deal with the fact Request.query_params
and Request.body_params
started to have a different nature. In fact, while the first it's still an object, the second one became an awaitable.
In order to avoid code complexity, and also push the developer to explicitly declare in the application code which request's component gets accessed by the code, we opted to drop the generic combined Request.params
attribute. This also gives more consistency to Emmett api's, since Request.params
behaviour was not exactly predictable when the same parameter name was present both in query_params
and body_params
.
This means that you need to change the code where you using Request.params
and use the other attributes. If you still want to make a combined version of the two, you can do it manually:
@app.route()
async def some_route():
params = {**request.query_params, **(await request.body_params)}
In Emmett the default value for the response Content-Type
header is now text/plain
instead of the previous text/html
.
All the automatic handling of this header done by the framework itself is still there, so even you're using a service
pipe or a template for your route, nothing will change.
The main difference is that you no more need to declare plain text Content-Type
if you just return a string in your route. You need to change the Content-Type
header just if you return strings containing html in your routes.
Aside from breaking changes, Emmett 2.0 also deprecated the run
command, in favour of two different commands for local serving and production serving your application. This means you should use:
emmett -a some_app develop
in order to run a local serveremmett -a some_app serve
in order to run a production serverWhile the run
command still works in Emmett 2.0, it will be completely removed in future versions.
Emmett 2.0 also introduces some new features:
output
parameter to route
decoratorafter_loop
signalEmmett 2.0 also introduces official support for Python 3.8.