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: r.value.structuredContent.projects.map(p => p.slug),
  };
});

Modern MCP Is A Fight

I really don’t want to talk too much about MCP here, but MCP is in fact a protocol that greatly benefits from Codemode. The problem in parts is that MCP in practice often targets harnesses that do not (yet?) use Codemode. But the tide is shifting. In the meantime, a temporary crutch has been to do what Cloudflare did, and do Codemode within the MCP server. But now we have Codemode in Codemode which is pretty bad. It means double JSON escaping, easy for smaller models to get confused by and the inner code cannot call the outer tools. So if you for instance use the Cloudflare MCP servers in Pi, the agent needs to write JavaScript and funnel it through more JavaScript. This is really not optimal, but it’s also understandable that this is happening:

const accRes = await tools.mcp__cloudflare__execute({
  code: `async () => {
    const r = await cloudflare.request({ method: "GET", path: "/accounts" });
    return r.result.map(a => ({ id: a.id, name: a.name }));
  }`,
});
const accounts = JSON.parse(accRes.content.map(c => c.text).join(""));

const out = [];
for (const account of accounts) {
  const r = await tools.mcp__cloudflare__execute({
    account_id: account.id,
    code: `async () => {
      const r = await cloudflare.request({
        method: "GET",
        path: \`/accounts/\${accountId}/workers/scripts\`,
      });
      return r.result.map(s => ({ id: s.id, modified: s.modified_on }));
    }`,
  });
  out.push({ account: account.name, workers: r.content.map(c => c.text).join("") });
}
return out;

MCP Desires

So to end things off: how well does Codemode work with MCP today? Well … not amazingly well. That’s because MCP servers are not really targeting harnesses that use Codemode yet (though at this point I think most harnesses support it).

For this to work well some recommendations:

Future of Codemode

So where does this leave us? Is this a reversal of what I wrote a year ago where I encouraged CLIs? I don’t think so. In fact, the MCP ecosystem from my perspective picked up on exactly what we pointed out a year ago works: code. But Codemode goes beyond MCP in that it can act as a capable mechanism within the harness to express more freedom for the agent.

There are however also some things that we still need to figure out. For one, durability with Codemode is trickier. We might have to adopt some ideas from durable workflow engines here to snapshot invocations. Or maybe, something like Starlark is a better composition language than JavaScript given its deterministic nature.

Images, binary data and just the inability of this pattern to work with smaller models is also something that needs to be fleshed out. So it’s for sure not a perfect solution yet, but it’s quite a useful pattern that I expect us to leverage more.

October 06, 2026 12:00 AM UTC

October 05, 2026


Tryton News

Security Release for issue 15032

Jaisurya has discovered that the content of the HTML editor was not escaped.

Impact

CVSS v3.0 Base Score: 5.4

Workaround

Setup restrictive CSP without unsafe inline script may prevent the attack.

Resolution

All affected users should upgrade trytond to the latest version.

Affected versions per series:

Not affected versions per series:

Reference

Concerns?

Any security concerns should be reported on the bug-tracker at https://bugs.tryton.org/ with the confidential checkbox checked.

1 post - 1 participant

Read full topic

October 05, 2026 06:00 AM UTC

Security Release for issue 15035

lizparadox_ has discovered that the report name can be used to execute commands on the server.

Impact

CVSS v3.0 Base Score: 6.8

Workaround

There is no workaround.

Resolution

All affected users should upgrade trytond to the latest version.

Affected versions per series:

Not affected versions per series:

Reference

Concerns?

Any security concerns should be reported on the bug-tracker at https://bugs.tryton.org/ with the confidential checkbox checked.

1 post - 1 participant

Read full topic

October 05, 2026 06:00 AM UTC

October 04, 2026


Mark Dufour

Shed Skin v0.9.14, v1.0 coming soon!

I have just released version 0.9.14 of Shed Skin, a restricted-python to C++ transpiler. Shed Skin allows one to effectively convert (or transpile) pure Python code to highly optimized machine code. This comes at the cost though of having to conform to a seriously restricted subset of Python features/libraries. Although, it is possible to generate extension modules, that can be used in larger, unrestricted, programs.

While the previous release was just a month ago, I felt like there were so many important improvements already that I didn't want them to wait for the 1.0 release which looks like it may finally happen within a few months! From a completely rewritten and faster, more scalable, type inference engine, to full Unicode support, to almost complete feature parity with Python 3.15 (for the supported modules), to support for heterogeneous 3-len tuples - it has been a hectic month :)

With all this in place, I'm starting to plan the final pieces I would like to see in place for a 1.0 release. There is still quite a bit of work left, but much of it is mechanical and just working through a long list of minor decisions and details.. It should be possible to have all that done around the end of the year.

I'm also happy to mention a relatively new but similar project here, TurboPython, which may be of interest to those reading this. The most interesting difference with Shed Skin is that it uses a RUST-like memory model: "TurboPython is a general-purpose, statically typed language that compiles Python through C++ to a native binary. It combines Python's syntax with an ownership model that gives memory safety and predictable performance—no garbage collector, no automatic reference counting, no GIL."

October 04, 2026 11:29 AM UTC

October 03, 2026


Brian Okken

PyBay 2026 Talk info and slides

Talk Title: Is TDD even relevant anymore? Yes, but don’t be dumb about it.

Event: PyBay 2026

Slides: tdd-pybay-2026.pdf

October 03, 2026 12:00 AM UTC

October 02, 2026


Python Software Foundation

Announcing the 2026 PSF Board Election Results!

The 2026 election for the PSF Board created an opportunity for conversations about the PSF's work to serve the global Python community. We appreciate community members' perspectives, passion, and engagement in the election process this year. 

We want to send a big thanks to everyone who ran and was willing to serve on the PSF Board. Even if you were not elected, we appreciate all the time and effort you put into thinking about how to improve the PSF and represent the parts of the community you participate in. We hope that you will continue to think about these issues, share your ideas, and join a PSF Work Group or PSF initiative if you feel called to do so.

Board Members Elect 

Congratulations to our three new and one returning Board members who have been elected! 

We’ll be in touch with all the elected candidates shortly to schedule onboarding. Newly elected PSF Board members are provided orientation for their service and will be joining the upcoming board meeting in October. 

Thank you!

We’d like to take this opportunity to thank our outgoing board members. Cheuk Ting Ho has been a super engaged PSF Board member, participating in many committees, and helping out on many PSF Programs and projects during her time on the PSF Board. Chris Neugebauer has been a longtime board member and in particular has been the watch guard of our bylaws conversations and has always been ready to share institutional knowledge. Denny Perez has been instrumental on the PSF Board, serving on the Executive Committee, as Treasurer, and on various committees during her tenure. All three of you helped shape the PSF’s Strategic Plan for the next 5 years, which was a massive undertaking. Thank you, Cheuk, Chris, and Denny for your leadership and dedication to the PSF and the Python community. You will be missed and are deeply appreciated!

Our heartfelt thanks go out to each of you who took the time to review the candidates and submit your votes. Your participation helps the PSF represent our community. We received 670 total ballots, easily reaching quorum–1/3 of affirmed voting members (1123). We’re especially grateful for your patience with continuing to navigate the additions to the elections processes with the inaugural Python Packaging Council election.

We also want to thank everyone who helped promote this year’s board election, especially Board Member KwonHan Bae, who took the initiative to cover this year’s election and worked with PSF Staff to conduct written interviews with candidates. This promotional effort was inspired by the work of Python Community News in 2023. We also want to highlight the PSF staff members and PSF Board members who put in tons of effort each year as we work to continually improve the PSF elections.

What’s next?

If you’re interested in the complete tally, make sure to check the Python Software Foundation Board of Directors Election 2026 Results page. These results will be available until November 10, 2026.

The PSF Election team will conduct a retrospective of this year’s election process to ensure we are improving year over year. We received valuable feedback about the process and tooling. We hope to be able to implement more changes for next year to ensure a smooth and accessible election process for everyone in our community. If you have feedback or comments about this year’s PSF Board election, we welcome you to join the discussion on discuss.python.org or email psf-elections@pyfound.org. 

Finally, it might feel a little early to mention this, but we will have at least 3 seats open again next year. If you're interested in running or learning more, we encourage you to contact a current PSF Board member or two this year and ask them about their experience serving on the board.

October 02, 2026 09:27 AM UTC


Talk Python to Me

#565: Tachyon, Python 3.15's Built-in Sampling Profiler

Do you know what's actually slow in your Python app? Or are you guessing? Until now, profiling Python meant a tracing profiler that made your code 2 to 3 times slower. Or a third-party tool that broke with every new release. Python 3.15 fixes that. It ships Tachyon, a sampling profiler built into the standard library. It attaches to live production apps with almost zero overhead. My guests are Pablo Galindo Salgado, CPython core developer and Steering Council member, and László Kiss Kollár from Bloomberg's Python infrastructure team. Their first prototype ran at two samples a second. Now it does over a million hz. And it lands in Python 3.15 this October.<br/> <br/> <strong>Episode sponsors</strong><br/> <br/> <a href='https://talkpython.fm/sentry'>Sentry Error Monitoring, Code talkpython26</a><br> <a href='https://talkpython.fm/devopsbook'>Python in Production</a><br> <a href='https://talkpython.fm/training'>Talk Python Courses</a><br/> <br/> <h2 class="links-heading mb-4">Links from the show</h2> <div><strong>Guests</strong><br/> <strong>László Kiss Kollár</strong>: <a href="https://www.linkedin.com/in/lkollar/?featured_on=talkpython" target="_blank" >linkedin.com</a><br/> <strong>Pablo Galindo Salgado</strong><br/> <br/> <strong>3.11</strong>: <a href="https://talkpython.fm/episodes/show/388/python-3.11-is-here-and-its-fast" target="_blank" >talkpython.fm</a><br/> <strong>Memray</strong>: <a href="https://talkpython.fm/episodes/show/425/memray-the-endgame-python-memory-profiler" target="_blank" >talkpython.fm</a><br/> <strong>PyStack</strong>: <a href="https://talkpython.fm/episodes/show/419/debugging-python-in-production-with-pystack" target="_blank" >talkpython.fm</a><br/> <strong>profile and cProfile</strong>: <a href="https://docs.python.org/3/library/profile.html?featured_on=talkpython" target="_blank" >docs.python.org</a><br/> <strong>py-spy</strong>: <a href="https://github.com/benfred/py-spy?featured_on=talkpython" target="_blank" >github.com</a><br/> <strong>Austin</strong>: <a href="https://github.com/P403n1x87/austin?featured_on=talkpython" target="_blank" >github.com</a><br/> <strong>PEP 799</strong>: <a href="https://peps.python.org/pep-0799/?featured_on=talkpython" target="_blank" >peps.python.org</a><br/> <strong>PEP 768</strong>: <a href="https://peps.python.org/pep-0768/?featured_on=talkpython" target="_blank" >peps.python.org</a><br/> <strong>PyCon US 2026 talk</strong>: <a href="https://us.pycon.org/2026/schedule/presentation/31?featured_on=talkpython" target="_blank" >us.pycon.org</a><br/> <strong>The docs</strong>: <a href="https://docs.python.org/3.15/library/profiling.sampling.html?featured_on=talkpython" target="_blank" >docs.python.org</a><br/> <strong>Backport to 3.14</strong>: <a href="https://github.com/pythonbackport/python-profiling?featured_on=talkpython" target="_blank" >github.com</a><br/> <br/> <strong>Watch this episode on YouTube</strong>: <a href="https://www.youtube.com/watch?v=yZdfOf8kQo4" target="_blank" >youtube.com</a><br/> <strong>Episode #565 deep-dive</strong>: <a href="https://talkpython.fm/episodes/show/565/tachyon-python-3.15s-built-in-sampling-profiler#takeaways-anchor" target="_blank" >talkpython.fm/565</a><br/> <strong>Episode transcripts</strong>: <a href="https://talkpython.fm/episodes/transcript/565/tachyon-python-3.15s-built-in-sampling-profiler" target="_blank" >talkpython.fm</a><br/> <br/> <strong>Theme Song: Developer Rap</strong><br/> <strong>🥁 Served in a Flask 🎸</strong>: <a href="https://talkpython.fm/flasksong" target="_blank" >talkpython.fm/flasksong</a><br/> <br/> <strong>---== Don't be a stranger ==---</strong><br/> <strong>YouTube</strong>: <a href="https://talkpython.fm/youtube" target="_blank" ><i class="fa-brands fa-youtube"></i> youtube.com/@talkpython</a><br/> <br/> <strong>Bluesky</strong>: <a href="https://bsky.app/profile/talkpython.fm" target="_blank" >@talkpython.fm</a><br/> <strong>Mastodon</strong>: <a href="https://fosstodon.org/web/@talkpython" target="_blank" ><i class="fa-brands fa-mastodon"></i> @talkpython@fosstodon.org</a><br/> <strong>X.com</strong>: <a href="https://x.com/talkpython" target="_blank" ><i class="fa-brands fa-twitter"></i> @talkpython</a><br/> <br/> <strong>Michael on Bluesky</strong>: <a href="https://bsky.app/profile/mkennedy.codes?featured_on=talkpython" target="_blank" >@mkennedy.codes</a><br/> <strong>Michael on Mastodon</strong>: <a href="https://fosstodon.org/web/@mkennedy" target="_blank" ><i class="fa-brands fa-mastodon"></i> @mkennedy@fosstodon.org</a><br/> <strong>Michael on X.com</strong>: <a href="https://x.com/mkennedy?featured_on=talkpython" target="_blank" ><i class="fa-brands fa-twitter"></i> @mkennedy</a><br/></div>

October 02, 2026 12:09 AM UTC


Python Insider

Python 3.15.0 candidate 3 is here!

Following tradition, a surprise rc3!

October 02, 2026 12:00 AM UTC

October 01, 2026


EuroPython

Humans of EuroPython: Diego Russo

This week we’d love to spotlight Diego Russo, a member of the Programme Team, and his contributions to EuroPython 2026.

In addition to helping ensure an interesting and varied conference programme, Diego was our liaison with Guido van Rossum, Łukasz Langa and Pablo Galindo Salgado who gave a very fun keynote session at the conference.

We&aposre so grateful for your contribution, Diego!

Cover image with Diego Russo, a member of the Programme Team at EuroPython 2026

EP: How were you involved in EuroPython 2026?

I was part of the Programme Team, managing the CfP, mentorship programme, speaker selection, waiting list and conference schedule. A big part of the work is keeping the programme balanced and free of gaps.

EP: Was there something specific about the Python community that made you want to give something back through volunteering?

I’ve used Python since 2006 and first attended EuroPython in Florence in 2011. I was struck by the community’s openness and welcoming spirit. Python conferences gave me a lot over the years, so in 2022 I decided to give something back by volunteering. Since then, I’ve worked with the Communications and Programme teams.

EP: What does the "invisible" side of EuroPython look like - the stuff that has to go right for everyone else to have a good time?

A lot of it is the schedule. We have several tracks and need to balance topics so people with different interests stay engaged. We receive far more proposals than we can fit, which lets us be selective but makes the choices difficult.

EP: What&aposs something you noticed about the conference because you were volunteering that you&aposd never have spotted as a regular attendee?

How many last-minute cancellations happen, and how quickly the team has to reshuffle the schedule and sometimes find replacement speakers during the conference itself.

EP: In what ways did volunteering shape or strengthen your ties to the community?

Python is more than a programming language to me. It has shaped my career to the point that I now contribute to CPython as a Core Developer, and the community played a major part in choosing that path.

EP: Is there a common myth about conference volunteering you&aposd like to set the record straight on?

I waited before volunteering because I thought I did not have enough experience. I was completely wrong. The EuroPython community made it easy to get involved from the start. If you are willing to help and learn, you gain experience along the way.

EP: What surprised you most about volunteering at EuroPython?

What surprised me most is how much volunteering changes the way you experience the conference. You stop seeing EuroPython only as an event you attend and start seeing the people, effort and collaboration that make it possible. It gives you a much stronger sense of belonging to the community.

EP: Thank you for your work, Diego!

October 01, 2026 11:56 PM UTC


Django Weblog

Nominate Someone for the 2026 Malcolm Tredinnick Memorial Prize

Hello Everyone 👋

It is that time of year again when we recognize someone from our community in memory of our friend Malcolm.

Malcolm was an early core contributor to Django and had a huge influence on the Django we know today. Besides being knowledgeable, he was also especially friendly to new users and contributors. He exemplified what it means to be an amazing Open Source contributor. We still miss him to this day.

The prize

Our prizes page summarizes it nicely:

The Malcolm Tredinnick Memorial Prize is a monetary prize, awarded annually, to the person who best exemplifies the spirit of Malcolm’s work - someone who welcomes, supports, and nurtures newcomers; freely gives feedback and assistance to others, and helps to grow the community. The hope is that the recipient of the award will use the award stipend as a contribution to travel to a community event -- a DjangoCon, a PyCon, a sprint -- and continue in Malcolm’s footsteps.

Please make your nominations using our form: 2026 Malcolm Tredinnick Memorial Prize nominations. Django Software Foundation board members, Django Fellows, and the Treasurer’s assistant are not eligible to receive the prize. You are still welcome to use the appreciation field below to recognize them, and we’ll make sure those messages are passed along.

We will take nominations until Saturday, October 15th, 2026, 23:59 Anywhere on Earth, and will announce the results in late October. If you have any questions, please use our dedicated forum thread or contact the DSF Board.

October 01, 2026 01:51 PM UTC


Python Insider

Python 3.10.22, 3.11.17, 3.12.15, 3.13.16 and 3.14.8 are now available!

Security updates across Python 3.10–3.14, the final maintenance release of 3.13, and a farewell to Python 3.10.

October 01, 2026 12:00 AM UTC


Graham Dumpleton

Getting to know Tachyon

Python 3.15 ships with a new profiler. It is called Tachyon, it lives in the standard library as the profiling.sampling module, and unlike cProfile it is a sampling profiler rather than a tracing one. I now have a set of hands-on workshops for it, which you can find at github.com/GrahamDumpleton/tachyon-workshops or on the workshops page of this site, and they have reached the point where I am happy for other people to do them. That said, they were not written for other people in the first place. They were written so I could learn Tachyon myself, and the reason I wanted to learn it had little to do with profiling as such.

Why I wrote them

For the past while I have been working on wrapture, a library built on top of wrapt for attaching bindings to arbitrary call sites in a Python program without modifying the code being observed, then doing something useful with what flows through those call sites, such as recording calls or exporting traces. Everything wrapture does happens from inside the process. It wraps functions, and when they are called it is there in the call path to see the arguments, the return value, the exception, and the time taken.

So when a profiler landed in the standard library the obvious question was whether wrapture could make use of it. Could Tachyon be offered as part of wrapture's tracing, so that a binding on a call site could also tell you what the program was doing underneath that call? Could the two share anything at all? I did not know, and reading the Tachyon documentation was not going to tell me, because the documentation describes what each option does and not what you would want it for. The only way I was going to get a real answer was to use every part of the profiler on real programs, and the way I have been doing that lately is to have workshops written for the thing I want to learn and then do the workshops.

Working from the outside

A sampling profiler does not instrument your program. Tachyon runs as a separate process, reads the call stack of the target process from the outside, a thousand times a second by default, and counts what it finds. The program runs at full speed and never knows it is being watched. The counts are then an estimate of where time went: a function that appears in half the samples took about half the time. cProfile, which in 3.15 has moved to profiling.tracing, does the opposite. It hooks every function call and return, so the counts are exact, but a program made of millions of small calls can run several times slower under it.

One of the early workshops puts the same program under both and the difference is stark enough that it answers the question of which to reach for most of the time. The other consequence of working from the outside is that the profiler needs permission to read another process's memory. Linux lets a user do that to processes they started themselves. macOS and Windows do not without root or administrator rights, which is why the workshops only run on Linux, and why on a Mac the way to run them is in a container. More on that below.

Where that leaves wrapture

My first impression, having now been through all of it, is that the two do not fit together. Tachyon works by looking in at a process from outside. wrapture works by being inside the process, in the call path. I have found nothing to suggest Tachyon can be driven from within the process it is profiling in the way wrapture would need, and nothing in the way it reads a process that a binding on a call site could hook into. The models are different enough that I cannot see a way of marrying them, at least not with what is in 3.15.

That is a perfectly good answer. I would rather know it now than have spent time trying to bolt one onto the other, and the reasoning behind it is grounded in having used every mode of the profiler rather than guessed from a page of options. If that changes in a later release, or if someone knows something about Tachyon's internals that I missed, I would like to hear about it. For now the question is parked, not closed.

Learning by having the lesson written

What surprised me was how much better this worked as a way of learning than reading the documentation would have. I did not write the workshops by hand. I described what I wanted each one to teach, had an AI build it, then did the workshop. The useful part was not that the AI knew Tachyon, since the documentation knew Tachyon just as well. It was that the AI kept filling in the context the documentation leaves out: why wall-clock time and CPU time disagree and what that disagreement tells you about the fix, why a view of which thread holds the GIL exists at all, why you would record a profile in the binary format and look at it later rather than looking at it now. Each feature came with a small program written to show it, which had the problem the feature was there to find, and that is what made the feature stick.

This is a different way to use an AI for learning than asking it questions. Asking questions gets you answers at the level of the question. Asking for the lesson to be built gets you something you then have to work through with your own hands, and if the lesson is wrong you find out when the check at the end of a step does not pass. It is also, I have noticed, far closer to how I actually learned things in the first place, which was by having to teach them.

What the workshops cover

There are two collections, with a catalog in the repository so that one URL offers both.

The first, "Profiling with Tachyon", is fourteen workshops and a little under four hours in total. It starts with running the profiler on a small report generator and reading the table it prints, so that you know what a sample is and why time is samples multiplied by an interval. It then puts a program under both the tracer and the sampler, and looks at how the profiler reads a process from outside and what that needs permission for. From there it moves through the pictures the profiler produces: the interactive flame graph, the heatmap that paints sample counts onto the source so a single expensive line stands out, and recording in the binary format to replay later as a table, a flame graph, a heatmap, a Firefox Profiler file, JSON lines or a pstats file. The next group is about choosing what to measure, with workshops on wall-clock against CPU time, threads and the GIL, the cost of exceptions, and async code profiled as tasks and awaits rather than as the event loop's stack. The last group looks closer, at the markers for native code and the garbage collector, at opcode-level profiling in the heatmap, at differential flame graphs for telling whether a fix worked, and at the live terminal view that watches one process like top.

The second, "Tachyon on real applications", is seven workshops and a little over two hours. Each one runs a realistic program in one terminal and drives it from a second, with the profiler between them. There is a Flask application under load, then a slow endpoint found with the flame graph, pinned to a line with the heatmap, fixed, and proven fixed with a second recording. There is a Starlette service under uvicorn whose searches stall when articles are read, including a fix with a thread that does not work and why, and one with a process pool that does. There are worker processes, both a process pool and gunicorn with three workers, profiled with --subprocesses. There is a pytest suite with the profiler wrapped around it, attaching to a server that was started without the profiler, and finally a profile recorded in production, carried back and compared in a notebook with a table and a chart.

Every workshop ships the program it profiles, and the flame graphs and heatmaps open in JupyterLab in a tab beside the code, so you are reading the picture and the source together rather than switching between a browser and an editor.

Running them

The workshops are built on jupyterlab-workshop, a JupyterLab extension that shows the instructions in a side panel with clickable actions that drive the session, opening files, running commands in a terminal and running cells in a notebook, and checks what you have done as you go.

The easiest way to start is the Binder button in the repository README, which builds the repository into a temporary JupyterLab on mybinder.org with nothing to install and no account needed. The session is thrown away when you are done, so finish a workshop in the session you started it in, and shut the session down from the Finish dialog or the File menu rather than just closing the tab, so the resources go back for other people. The Codespaces button does the same in a container tied to your GitHub account, which persists until you delete it and uses your account's monthly allowance.

If you want to run them on your own machine and that machine is a Mac, the Linux requirement means a container. The repository carries a Dockerfile for it, with Python 3.15, JupyterLab, the extension and a non-root user, and the checkout is mounted so everything the workshops write ends up in your checkout:

git clone https://github.com/GrahamDumpleton/tachyon-workshops
cd tachyon-workshops
docker build -t tachyon-workshops -f container/Dockerfile .
docker run --rm --init -it -p 127.0.0.1:8888:8888 -e JUPYTER_TOKEN=tachyon \
    -v "$PWD":/home/learner/tachyon-workshops tachyon-workshops

Then open http://127.0.0.1:8888/?token=tachyon. On Linux with Python 3.15 and uv you can skip the clone entirely and launch the extension on the catalog directly, with a directory of your own to keep the workshops in:

uvx --python 3.15 --from "jupyterlab-workshop[lab]" jupyter-workshop launch \
    --root ~/training --catalog https://raw.githubusercontent.com/GrahamDumpleton/tachyon-workshops/main/catalog.json

Python 3.15 is named because each workshop builds its own environment from the Python that JupyterLab runs on, and the profiler and the program it profiles must be the same Python. If you already have the extension installed somewhere, you can also just subscribe to the catalog from the workshop browser and the collections are offered there.

What's next

Whether Tachyon ends up having anything to do with wrapture, I know a good deal more about it than I did, and I have something to show for the time that other people can use. If you do the workshops and find a step that is unclear, or one that does not work for you, the issue tracker is the place to say so. Fingers crossed they are as useful a way into Tachyon for you as writing them was for me.

October 01, 2026 12:00 AM UTC

September 30, 2026


"Michiel's Blog"

Monitoring my Samsung smart washing machine without a cloud

Two weeks ago I gave a talk at Python Leiden on how I monitor my Samsung Smart washer without using Samsung SmartThings.

As Samsung announced using their SmartThings API will start costing USD 5 per month starting October, and today is the last day of September, I thought it would be appropriate to write a blog post based on my talk now.

I have a Samsung washing machine

My washing machine

I bought it in May 2021 for 450 euros. It plays Die Forelle whenever the wash is finished. But it’s in my garage, so I can’t hear it when I’m in my living room or home office. Also, it is “smart”. It calculates how long the cycle is going to take, depending on the load and I think also depending on how dirty the water is. This means that if you start a cycle, it tells you that it’s done in 90 minutes, and if you’d come back 90 minutes later it might still need 30 minutes, or it might be that it’s already done for 15.

It can send you notifications via the SmartThings app. This app requires a whole bunch of permissions, and takes up a whopping 857MB on my iPhone. It gets the data from the SmartThings cloud. So my washer talks to SmartThings, tells when it thinks the wash is done, and my phone talks to the same cloud if I open the app to see how the laundry is going and to send me notifications when it’s done. I don’t really like this much.

Samsung has just sent an update to some of their smart fridges bricking them for a couple of days. I don’t like that much either.

But you could use Home Assistant!

Yeah I know, Home Assistant. Every tinkerer’s favorite platform for home automation. Which you can install on a Raspberry Pi or on their own bespoke hardware. Indeed, it has a SmartThings integration. Unfortunately, this still works via Samsung’s cloud. So my washer still needs to talk to SmartThings, and my Home Assistant server would need to get its data from the Samsung Cloud. Moreover, Samsung decided to start asking money for the cloud access: 5 USD per month. As you see, it’s quite a popular integration too, with over 10% of Home Assistant users having this installed.

SmartThings warning Home Assistant

So if we calculate quickly, if I’d have paid 5 USD a month since I had this washer, it would have cost me €450 for the washer plus $320 for cloud access! That seems a little out of balance.

So what is my goal, anyway?

I would like to see when the wash is going to be ready. I’d want to have a small display that I can place in my living room that can show what is on the display of the washing machine, how many minutes are left. This way I can quickly estimate if I run an errand now or I’d wait until the wash is ready, first. I’d not want to use their app for this, nor their cloud.

My approach

I checked if there is any possibility for local access. I remembered when I read about the announcement of the Matter protocol, which would allow interoperability, and Samsung being one of the backers of this protocol, and me being happy about the fact that I bought a Samsung washer. Silly me.

It turns out Samsung indeed delivered Matter support. What they made is a feature where if you’d have a Matter lightbulb (or washer), you can control that from their SmartThings platform. But it does not work the other way around! My smart washer does not expose itself as a Matter device on the network. It does not expose any services on the network. Trust me, I used Wireshark!

This as opposed to my Brother printer, for instance, which has an open website and even an endpoint that exposes data as a .csv, which will happily show off how low the toner is, how many prints it made in its lifetime or even how many paper jams it had (a not insignificant amount).

Samsung could very easily have built something like that. But they don’t. Because they want me in their SmartThings ecosystem, sending my personal data, and possibly fetching my money, too.

So I decided to take an alternate approach: put a camera in front of the machine, take a picture, read it with computer vision, parse it and make it available over an HTTP endpoint on my local network. This turned out not to be so easy. Originally I was planning to use a Raspberry Pi that I still had lying around (who doesn’t have one?) with an old Logitech 720P webcam. But the webcam has a fixed focus distance, and a rather low resolution. It did not turn out well.

Low res

Then I decided to shell out for the Raspberry Pi Camera Module 3 which is the version that has autofocus. It is around 35 euros. And while it works well, only after I installed it I realized I could probably just as well have used an old Android phone with shattered screen. Because those also have decent cameras plus autofocus.

Higher res

And then I breadboarded together a Raspberry Pi Pico plus a 1602 LCD plus a buzzer, a few LEDs, and a push button. The Pico is a small programmable microcontroller that is great for things like this. It is low cost, runs MicroPython if you want, so what’s not to like? This display connects to wifi, gets the state of the washer and shows it on the screen. If there is no wash, it just acts as a clock. Pretty practical and I like it.

Pico with display

I needed the buzzer just so it could play Die Forelle when the laundry is ready ;-)

My thoughts

It would have been so much better if Samsung would have exposed this data on my wifi network!

Please note that this integration only allows me to read out the state of the washer, I can’t remote pause or add bubbles or whatever other features that are available via their app and that I would not ever use.

One of my coworkers explained he has a similar washing machine and he hooked it up to Home Assistant by simply connecting it to a smart plug that exposes the current draw. When there is no more current draw, he knows the washer is ready. This works, but my problem is that it does not help me with the question when the wash is going to be ready.

Because I now have the data, I could make a nice graph that shows the washing machine during its cycle adjusting the ETA a couple of times. Here’s an example of where it ended earlier than originally planned. But it can also end much later!

Washer countdown graph

I’d still like to print a 3D case for the display. And I thought that it might be nice if I’d actually expose a Matter service on my Pi, so one could integrate it in Home Assistant after all. However, I currently do not run Home Assistant myself and also as far as I could see there is no great Python library that I could use for this.

All in all, it was a fun project!

Discussion on Hacker News

September 30, 2026 08:00 PM UTC


Python Insider

Python Language Summit 2026

The 2026 Python Language Summit was hosted in Kraków, Poland as part of EuroPython 2026. There were 15 talks covering free-threading, Rust, garbage collection, type annotations, and more.

September 30, 2026 12:00 PM UTC

Lightning Talks (Python Language Summit 2026)

Lightning talks on a one-time ABI break, safer interruptions, EktuPy (Scratch but Python), an AGENTS.md file for CPython, and a call to read PEP 836.

September 30, 2026 12:00 PM UTC

PEP 827: Type Manipulation (Python Language Summit 2026)

Michael J. Sullivan presents PEP 827 and discusses a key design decision: how to store type annotations?

September 30, 2026 12:00 PM UTC

Free-Threaded Python Post-Era (Python Language Summit 2026)

Tobias Wrigstad, Fridtjof Stoldt, and Donghee Na propose a safe and performant, high-level concurrency model for free-threaded Python

September 30, 2026 12:00 PM UTC

Developer-in-Residence Update & Future (Python Language Summit 2026)

Petr Viktorin gives an update on the Developer-in-Residence role and asks Python core developers for projects to prioritize

September 30, 2026 12:00 PM UTC

Spicycrab (Python Language Summit 2026)

Kushal Das shows off Spicycrab, a Python-to-Rust transpiler for Python users who need performance without learning Rust or leaving Python

September 30, 2026 12:00 PM UTC

Rust for CPython (Python Language Summit 2026)

David Hewitt shares a status update, first module, and potential acceptance criteria for the Rust for CPython project

September 30, 2026 12:00 PM UTC

Memory Snapshots for CPython (Python Language Summit 2026)

Hood Chatham proposes memory snapshots and an initialization phase for speedier Python startups

September 30, 2026 12:00 PM UTC

Memory Buffer Protocol (Python Language Summit 2026)

Nathan Goldbaum proposes safe concurrent access through buffer leases and custom data types for the Python Buffer Protocol.

September 30, 2026 12:00 PM UTC

Garbage Collection: Generational? Incremental? Both! (Python Language Summit 2026)

Mark Shannon proposes a future garbage collection strategy for Python following the revert of the incremental garbage collector in Python 3.14

September 30, 2026 12:00 PM UTC