skip to navigation
skip to content

Planet Python

Last update: October 06, 2026 04:48 PM UTC

October 06, 2026


Tryton News

Tryton Release 8.2

We are proud to announce the 8.2 release of Tryton.
This release provides many bug fixes, performance improvements and some fine tuning.
You can give it a try on the demo server, use the docker image or download it here.
As usual upgrading from previous series is fully supported.

Here is a list of the most noticeable changes:

Changes for the User

Client

When deleting or removing multiple rows from a list widget, a popup is displayed to confirm the selection that will be deleted or removed.
And when clicking on delete or remove from a list widget with no row selected, a search popup is raised to search and select the rows to delete or remove.

When selecting multiple rows for editing from a One2Many widget, the edition popup loops over each record. This is a faster and more reliable way to perform a mass edition.

The login services can now display an icon to represent the service provider. This makes it easier for users to select the right services.

The Binary widget supports filtering the file selection per file extension and mime type.
And when the field has no file name defined, the widget will use the record name as a fallback file name when the content is downloaded.

It is now possible to mark as read a set of notifications.

Accounting

The “Open Journal” menu entry has been replaced by the “Lines” menu entry which uses a journal and period header as default values.
This new menu entry also provides a new way to search for accounting lines.

We now use the maturity date before the effective date to calculate the reconciliation date.

The wizard to create dunning allows limiting the creation to a selection of companies.

The wizard to create direct debits allows limiting the creation to a selection of companies.

When posting an invoice, the system will check the validity of the European tax identifiers used.

A relate is now available from the period and the fiscal year to open the related invoices.
This is useful when you need to collect all the invoices done in a period, for example to send to an external accountant.

The SEPA payment module now includes the flavors pain.001.001.09 and pain.008.001.08.

A generic CSV format has been added to import statements. You can configure the format to map columns to the Tryton fields.
It is common that banks provide only a custom CSV format for the statements.

It is now possible to create tax rules based on organizations.
This is useful for example to create a single rule that applies to all European countries.

E-document

PEPPOL

We added a configurable processing delay on each PEPPOL service.
This prevents sending the newly posted invoice directly and allows some time for the user to correct any mistakes.

UN/CEFACT

Tryton can now parse the UN/CEFACT invoice to create a supplier invoice.
This will be useful to implement French e-invoicing.

Party

The wizard to check VAT numbers with the VIES service has been replaced by an automatic background task.
Once a number has been validated, it is considered valid for a configurable period before being re-validated.

When entering a contact mechanism, the system will try to guess the type.

Product

The products can now be added or removed directly from the category form.

Production

A tolerance can now be configured on each BoM. It raises warnings when the input or output quantities deviate from the calculated quantities from the BoM. It also detects additional or missing products.

Purchase

Tryton now calculates the actual average lead time of each product supplier over the last year.
This is useful to update the configured lead time or to find poor suppliers.

Sales

It is now possible to configure sales to create customer shipments only in draft (instead of waiting).
This is useful when the workflow requires a manual validation of the shipment before being processed.

The wizard to create invoices and consumptions of subscriptions allows limiting the creation to a selection of companies.

A stock lot can now be set on the sale line from a POS.
This is useful for businesses that require tracking the lot sold to customers.

Stock

Tryton now warns when an inventory modifies the quantities or costs too much.
The variations are now displayed with a visual hint when they are close to the tolerance.

A scheduled task has been added to create stock periods automatically using a configured interval. And another task closes them after a configured delay.
This removes the need to manually close stock periods, which is important for performance.

Web Shop

A scheduled task has been added to cancel abandoned sales after a configured delay per web shop.
This prevents the list of draft sales from increasing too much.

New Modules

Account Invoice Factur-X

The Account Invoice Factur-X Module allows to generate invoices in Factur-X format.

Document Incoming OCR Eagle Doc

The Document Incoming OCR Eagle Doc Module provides integration with Eagle Doc services.

Project Disbursement

The Project Disbursement Module provides support for paying and invoicing disbursements per project.

Stock Conversion

The Stock Conversion Module transforms one product into another with ease.

Changes for the System Administrator

Server

The “Administration” group has been replaced by an “Administrator” flag on the user.
This ensures that an administrator always has access to everything even if a resource access is restricted to a group (which was not the “Administration” group).

The trytond-admin command can now manage any user. This means that it can create a new user, activate or deactivate, promote as administrator or demote from administrator, set the email or password and send a reset password email.

The unfinished queued tasks are now retried automatically by a scheduled task.

A timer is now attached to every RPC request with a timeout defined. It ensures that the request does not last longer by raising a TimeOutException.

:warning: The webhooks must be updated to include the /r/ prefix inherited from the new Router.
We have kept the former routes for backward compatibility.

Accounting

The Stripe API has been updated to the version 2026-09-30.endive.

Stock

The DPD Shipment Service API has been updated to version 4.5.

Changes for the Developer

Server

The readonly attribute and states are now enforced on the server-side when checking the access rights of the user.

The fields have a new editable states which completes the existing readonly but it is not enforced, only used for UI purposes.

We added BulkBuffer for creation, deletion, save or any function on ModelStorage.
The BulkBuffers are context managers that are flushed when reaching a defined size by calling a function with the current content.
This is useful when looping on a large set of records to limit the memory consumption.
Ex:

# records are saved every 2000 records
with Model.bulk_save() as save:
    for record in records:
         record.amount += 10
         save.push(record)

A new router type of object is now supported in the Pool.
A Router exposes entrypoints but as it is registered in the Pool it can be extended by other modules.
The entrypoint of a Router is only registered for the database for which the origin module is activated.

A route has been added for the custom.js and custom.css files of sao. It chains a list of files that can be extended by any module.

The ORM is now using a BrowseList instead of a simple list of instances. The BrowseList allows keeping the cache and prefetching aligned with its content when it is mutated, for example by .sort().

It is now possible to define the filename extension of Binary fields.
And it is also possible to set filters on the binary and image widgets.

The database connection cursor now supports row factories like dict_row, namedtuple_row and scalar_row. They change the default tuple type of fetched records.
Thanks to the row factory, the cursor_dict has been removed.

It is now possible to configure Mixins to apply to the Database and TableHandler of the backend.
This feature is now used for the GIS backend.

We added support for AGE to the SQLite backend.

The DBTestCase is now public and can be reused by modules. It is useful to create test cases based on a module but without the generic tests from ModuleTestCase.

The XML record tag now supports a search attribute. The value is a domain used to search for existing records and reuse them when the id is not yet known.
This is useful for example to create a country record which may have been already created.

The convert module gains an import_xml function which can be used to import XML files into the database (like the file declared in the module).
It can be useful for tests that need to have such records created.

The email validation tools now have a check deliverability option.

The URLAccessor can now accept a request parameter to use instead of the Transaction.context.

The ResourceAccessMixin gains two new fields last_user and last_modification.

Proteus the scripting client

When configured with trytond (on the server host), it allows controlling access checks with the _check_access contextual keyword.

Company

The companies and employees are now in the user context.
This is useful to write domains that restrict a selection to only allowed companies or employees.

1 post - 1 participant

Read full topic

October 06, 2026 04:00 PM UTC


Django Weblog

Django security releases issued: 6.1.2, 6.0.9, and 5.2.18

In accordance with our security release policy, the Django team is issuing releases for Django 6.1.2, Django 6.0.9, and Django 5.2.18. These releases address the security issues detailed below. We encourage all users of Django to upgrade as soon as possible.

CVE-2026-77050: Potential denial-of-service vulnerability in get_supported_language_variant()

django.utils.translation.get_supported_language_variant() was subject to a potential denial-of-service attack when processing many distinct, very long language codes. Language codes were used as keys in an in-memory cache before their length was limited, potentially consuming excessive process memory.

To mitigate this vulnerability, language codes longer than 500 characters are now rejected or truncated before the cached lookup.

This issue has severity "low" according to the Django security policy.

Thanks to Gleb Lizunov for the report.

CVE-2026-84429: Potential denial-of-service vulnerability in HTTP header parsing

django.utils.http.parse_header_parameters() was subject to a potential denial-of-service attack due to quadratic time complexity when parsing a value with many separators inside a quoted parameter. An unauthenticated request could reach this parsing through headers such as Accept or Content-Type, for instance via the content negotiation performed by HttpRequest.accepts(). The per-call length limit does not bound the combined size of repeated headers.

The undocumented django.utils.http.parse_header_parameters() function now uses Python's email.message.Message for parsing. As a result, parsing of some malformed or unusual header values may differ, for example, RFC 2231 values with a missing encoding are now decoded.

This issue has severity "moderate" according to the Django security policy.

Thanks to Jisung Chae for the report.

CVE-2026-87890: Potential request forgery via spatial lookup byte values

Spatial lookups accepted raster values provided as bytes without requiring them to be explicitly wrapped in django.contrib.gis.gdal.GDALRaster. Although these values were opened through GDAL's in-memory virtual filesystem, they could contain a VRT document referencing an external raster source. This could cause GDAL to issue network requests as the Django process user while preparing the lookup.

This issue could be exploited by applications that passed attacker-controlled bytes directly to a spatial lookup. It was overlooked in the fix for CVE-2026-15307.

To mitigate this issue, raster values provided as bytes must now be wrapped in GDALRaster before being used in spatial lookups. Byte values representing valid hexadecimal geometries remain accepted.

This is a backward incompatible change. As a reminder, all untrusted user input should be validated before use.

This issue has severity "moderate" according to the Django security policy.

Thanks to sicksec for the report.

CVE-2026-87975: Privilege abuse in model formsets with editable primary keys

Model formsets incorrectly allowed forged POST data to either delete instances outside the limiting queryset or create instances via edit-only formsets when the model's primary key could be set through the form, such as with: a OneToOneField (or parent link used as the primary key of an inline formset's model), or a natural or UUID primary key included in the form's fields. Models using the default BigAutoField primary key were not affected.

This issue has severity "moderate" according to the Django security policy.

Thanks to Seonggwon Yoon for the report.

Affected supported versions

Resolution

Patches to resolve the issue have been applied to Django's main, 6.1, 6.0, and 5.2 branches. The patches may be obtained from the following changesets.

CVE-2026-77050: Potential denial-of-service vulnerability in get_supported_language_variant()

CVE-2026-84429: Potential denial-of-service vulnerability in HTTP header parsing

CVE-2026-87890: Potential request forgery via spatial lookup byte values

CVE-2026-87975: Privilege abuse in model formsets with editable primary keys

The following releases have been issued

The PGP key ID used for this release is Sarah Boyce: 3955B19851EA96EF

General notes regarding security reporting

As always, we ask that potential security issues be reported via private email to security@djangoproject.com, and not via Django's Trac instance, nor via the Django Forum. Please see our security policies for further information.

October 06, 2026 01:00 PM UTC


Armin Ronacher

What is Codemode

More than a year ago I wrote a few posts here that recommended people not to load custom tools into their context (or MCP servers) but to just use more scripts. Most importantly I wrote that Code Is All You Need and I wrote about that MCP needs code. With Pi 1.0 we now added MCP support via Codemode which in some ways is a long time coming, but then also maybe somewhat surprising to some. So I want to share some updated thoughts on this blog on what this all means.

What Are Tools

When a harness like Pi provides tools for an LLM to call, it does so by supplying some tool definitions which then translate into some token structure on the server side. Whether a model is encouraged to call a tool is the result of the reinforcement learning process. Something I wrote about before if you want to learn more.

One of the reasons we strongly lean towards CLI and bash is because it allows easy composition of calls, and because the model also learns how the file system works when it’s trained. So when it invokes a tool like echo foo > /tmp/test.txt the model also learns that after that tool call, there is now a file called test.txt in /tmp.

However bash has one fundamental limitation which is that it can only compose programs that run. And there are some things, which are not programs, but native tools to the LLM and they sort of have to be.

The most obvious example here is read or view_image. If a multimodal model needs to read an image, it cannot use cat for that because the harness needs to inject the actual image payload into the protocol of the LLM.

Another quite vivid example are sub agents. In order to spawn and orchestrate sub agents, it’s tricky to avoid tools that are provided by the harness. While in theory the agent could provide a CLI tool that talks to the outer harness via environment variables and Unix sockets, it’s a rather crude process. It however has another issue, and that is where the code runs.

Brains vs Hands

To better understand that, it’s important to think a bit more about where all the bits and pieces run. There really usually are two different systems involved. The first is the brain, the harness: it runs on one machine. It’s trusted. The second is often the same machine, but it’s really where the tools are executing: the hands. In Pi we now call this the execution environment, but you can think of it as the target of all the operations.

Crucially what is important for us, is that there is a dividing line between the harness brain and the target environment that runs bash and executes the tools.

And splitting this in half has some really important consequences. For a start it means that they are running on different file systems and they have different levels of trust. If you for instance use a sandboxing solution like Gondolin your bash stuff will be sandboxed just fine, but the harness itself will not be.

Orchestrating The Harness

Which brings us to what Codemode really does: it’s a way for the LLM to express and orchestrate complex operations on the harness side, but not the execution environment side. Codemode runs in the harness, in its own sandbox. In case of Pi it’s running in QuickJS within a WASM runtime with intentional limitations: no network, no file system, no timers, limited RAM. The only way is to call more tools. You could also imagine that Codemode could run Scheme or some other language as well.

If you are not familiar with Codemode, it’s basically just a way to issue tool calls from within some language, in our case JavaScript. That allows you to compose those calls without necessarily going through the LLM’s context. Credit for naming goes to our friends at Cloudflare who coined it.

For instance if you issue a bash call as a regular tool call in the LLM, then we only throw the trailing 2000 lines into the context and if the agent wants more, it needs to look at the overflow file itself. If however the agent issues that invocation via Codemode, then the Codemode side gets larger outputs sent structurally.

Most importantly, because Codemode is JavaScript the agent can express concurrent operations and basic workflows. A common way in which you see agents now use this, is to first probe at 5-10 items from some tool response to see what it looks like, and to then write a Codemode script that processes the next n items.

Codemode also allows you to throw state into the transcript! That means that one Codemode invocation can stash away data, that the next call in the session can load again. And remember: this is on the harness host, not the sandbox.

In case of Pi, Codemode also allows you to issue calls that naturally do not make any sense in Pi’s traditional interface. For instance if you want to generate images with an image model or you want to classify some text with a one shot classifier model, those Pi APIs are exposed via Codemode, but not via regular tools where they would just waste context.

What It Looks Like

So now that we talked a bunch about it, it’s probably worth being a bit more explicit about it. Let’s walk ourselves through some invocations of Codemode of recent Pi sessions of mine. Note that none of this code is human written. It’s from real sessions of Pi, just re-indented for your viewing pleasure. The agent starts using Codemode automatically either because it’s a task where the model already naturally picks up that tool, or because a user asked it to.

Note that Codemode is by default only enabled in Pi when MCP is enabled, but you can turn it on with "defaultTools": ["+codemode"] in the settings. Just ask Pi to enable it for you.

Generating Images

Let’s start simple with image generation. Image generation is a feature that Pi supports in the AI SDK core, but it’s not a tool that the agent can use. In the past the only way to use image models has been to write a bespoke extension or to have the agent run node itself and use the internal image APIs. However because we expose quite a few of the internal model APIs within Codemode, it means that the agent can use it:

const [painter] = await models.getAvailableOfType("image");
const result = await models.generateImages(painter, {
  input: [{ type: "text", text: "A cute little puppy sitting on a grassy " +
    "lawn, soft natural light, photorealistic" }],
});
if (result.stopReason !== "stop") return result.errorMessage;

for (const block of result.output) {
  if (block.type === "image") image(block);
  else text(block.text);
}

Note that the call to image() sends the image back as image content to the LLM. On the harness side it feeds it directly into both the agent, as well as onto disk as a temporary artifact in case the agent wants to be able to pass that image back to bash.

Classifying Things

Similar things apply to classifier models such as Jev. They also do not fit well into the workflows of an agent through the typical tools. But rather than making a bespoke tool available, Codemode just allows the agent to reach into the AI SDK and invoke those directly. Here you can see how Jev is used to mass process GitHub issues for a quick sentiment analysis:

const jev = await models.getModelOfType("classifier", "typesafe", "jev-latest");
const r = await tools.bash({
  command: "gh issue list --state open --limit 100 " +
    "--json number,title,body,comments",
});
const issues = JSON.parse(r.output);

const results = await Promise.all(issues.map(async (issue) => {
  const res = await models.classify(jev, {
    state: {
      title: issue.title,
      body: (issue.body || "").slice(0, 4000),
      comments: issue.comments.slice(-5).map(c => c.body.slice(0, 800)),
    },
    questions: {
      sentiment: {
        type: "choice",
        instructions: "What is the overall sentiment of the author towards pi?",
        criteria: {
          positive: "Appreciative, happy, constructive praise",
          neutral: "Matter-of-fact report or request without emotion",
          negative: "Frustrated, annoyed, upset, or angry",
        },
      },
      frustration: {
        type: "score",
        instructions: "How frustrated is the reporter?",
        criteria: ["not at all", "mildly", "clearly frustrated", "very angry"],
      },
      kind: {
        type: "choice",
        instructions: "What kind of issue is this?",
        criteria: {
          bug: "Bug report or regression",
          feature: "Feature request or enhancement",
          question: "Question or support request",
          other: "Docs, discussion, meta, spam",
        },
      },
    },
  });
  if (res.stopReason !== "stop") {
    return { n: issue.number, title: issue.title, error: res.errorMessage };
  }
  return { n: issue.number, title: issue.title, ...res.answers };
}));

store("sentiment_results", results);
return results
  .filter(r => !r.error)
  .sort((a, b) => b.frustration.score - a.frustration.score)
  .slice(0, 12)
  .map(r => `#${r.n} ${r.frustration.score.toFixed(2)} [${r.kind.choice}] ${r.title}`);

Note how in that above example we also call store() which dumps the result of that execution into the session transcript. A future invocation of Codemode can thus read back that result if it wants to.

The Promise.all here is fine, because Pi limits the total number of concurrent tool executions itself to four and maintains a queue for the rest.

A more adventurous example is to use Jev to drive a game engine for debugging purposes:

Codemode with Jev for Game Debugging

Here it knows about my tankctl command and it built itself quickly a minimal harness around it to drive a game loop to assist a user with debugging a problem. Note how it built a 30 step loop in which each step goes back to both the game engine to get a text dump of what’s going on, and then to Jev to determine what to do next:

const jev = await models.getModelOfType("classifier", "typesafe", "jev-latest");
const tank = async (cmd) =>
  (await tools.bash({ command: `tools/tankctl "${cmd}"` })).output;
await tank("start --map assets/maps/night_arena.map");

const questions = {
  action: {
    type: "choice",
    instructions: "You control the tank '@' in a top-down tank game. " +
      "Choose the best next action.",
    criteria: {
      attack: "an enemy has line of sight to you and you can fire at it",
      approach: "no enemy has line of sight; drive toward the nearest enemy",
      dodge: "an enemy shot is heading at you and will hit soon",
      powerup: "a powerup is close and no enemy threatens you",
    },
  },
};

function commandFor(choice, st) {
  const p = st.player;
  const enemy = st.enemies.filter(e => !e.dead)
    .sort((a, b) => (b.los - a.los) || (a.dist - b.dist))[0];
  if (choice === "attack" && enemy) {
    return `fire_at tank ${enemy.id}; frames 30 until clear,damage,kill`;
  }
  if (choice === "dodge") {
    // move perpendicular to the closest incoming shot
    const s = st.projectiles.filter(s => !s.yours)
      .sort((a, b) => a.eta - b.eta)[0];
    const dir = s && Math.abs(s.vel[0]) > Math.abs(s.vel[1])
      ? (p.pos[1] > s.pos[1] ? "+down" : "+up")
      : (p.pos[0] > (s ? s.pos[0] : 0) ? "+right" : "+left");
    return `input ${dir}; frames 20 until damage; input stop`;
  }
  const powerup = st.powerups.filter(u => u.available)
    .sort((a, b) => a.dist - b.dist)[0];
  if (choice === "powerup" && powerup) {
    return `goto ${powerup.pos[0]} ${powerup.pos[1]} 180`;
  }
  return enemy ? `goto ${enemy.pos[0]} ${enemy.pos[1]} 90` : null;
}

const log = [];
for (let step = 0; step < 30; step++) {
  const st = JSON.parse(await tank("state"));
  if (st.state !== "playing") break;
  const threats = st.projectiles
    .filter(s => !s.yours && s.miss_dist < 1.5 && s.eta < 1.5)
    .map(s => `incoming shot dist ${s.dist} eta ${s.eta}s`)
    .join("\n") || "no incoming shots";
  const r = await models.classify(jev, {
    state: { map: await tank("view 8"), threats, hp: st.player.hp },
    questions,
  });
  if (r.stopReason !== "stop") {
    log.push(`#${step} classifier error: ${r.errorMessage}`);
    break;
  }
  const choice = r.answers.action.choice;
  const cmd = commandFor(choice, st);
  if (!cmd) break;
  log.push(`#${step} hp=${st.player.hp} ${choice} -> ${await tank(cmd)}`);
}
return log.join("\n");

Calling MCP Servers

Lastly, Codemode obviously is great for calling MCP servers. And because we do not actually expose any of the MCP tools to the LLM, the agent first uses provided APIs to issue a tool search within Codemode to discover what it might be able to do with the connected servers. This form of progressive discovery makes the whole MCP business work well enough for a lot of use cases today.

Here for instance you can see the agent reach for the Sentry MCP straight away, even without discovering the tools, presumably because it has learned during the RL process already about what the Sentry MCP looks like. But it learns from what we inject into the system prompt, that the Sentry server is available to begin with. It’s not completely guessing here.

const orgs = await tools.mcp__sentry__find_organizations({});
const { organizations } = orgs.structuredContent;
const results = await Promise.allSettled(organizations.map(org =>
  tools.mcp__sentry__find_projects({
    organizationSlug: org.slug,
    regionUrl: org.regionUrl,
  })
));
return organizations.map((org, i) => {
  const r = results[i];
  if (r.status !== "fulfilled") return { org: org.slug, error: String(r.reason) };
  if (r.value.isError) return { org: org.slug, error: r.value.content };
  return {
    org: org.slug,
    projects: