Python - Bixoto Tech Blog https://tech.bixoto.com/category/python/ Thu, 04 Dec 2025 11:17:34 +0000 en-US hourly 1 https://wordpress.org/?v=6.3.1 https://tech.bixoto.com/wp-content/uploads/2023/10/favicon-bt.png Python - Bixoto Tech Blog https://tech.bixoto.com/category/python/ 32 32 Migrating commands from Poetry to uv https://tech.bixoto.com/migrating-commands-from-poetry-to-uv/ Thu, 04 Dec 2025 11:14:50 +0000 https://tech.bixoto.com/?p=176 At Bixoto we used to use Poetry to manage all our Python projects, but we recently started to migrate to uv, which is a lot faster. However, the commands of each tool don’t always match, which can cause some confusion. In this post we provide a partial cheatsheet of the most common commands and their … Read more

The post Migrating commands from Poetry to uv appeared first on Bixoto Tech Blog.

]]>
At Bixoto we used to use Poetry to manage all our Python projects, but we recently started to migrate to uv, which is a lot faster. However, the commands of each tool don’t always match, which can cause some confusion. In this post we provide a partial cheatsheet of the most common commands and their equivalents.

Initialize a repository

  • Poetry: poetry init
  • uv: uv init --bare

uv creates a README.md and a main.py by default; you must use --bare to disable this behavior.

Install dependencies

uv doesn’t have a simple install command, you must use sync with --frozen (to prevent it from updating packages) and --inexact (to keep superfluous packages, which is the default behavior of Poetry).

  • Poetry: poetry install
  • uv: uv sync --frozen --inexact

To synchronize dependencies and remove superfluous packages, use --frozen only:

  • Poetry: poetry sync
  • uv: uv sync --frozen

Update

Poetry has the update command to both update all dependencies or just one, but with uv it’s different:

  • Poetry: poetry update (all dependencies), poetry update <package> (single package)
  • uv: uv lock --upgrade (all dependencies), uv lock --upgrade-package <package> (single package)

Note: in both cases, uv has a shorter option: -U and -P.

Add dependencies

Poetry and uv have the same command to add dependencies, but the option differs when you want to use a different index:

  • Poetry: poetry add [--source $INDEX] ...
  • uv: uv add [--index $INDEX] ...

Remove dependencies

Here too the command are the same:

  • Poetry: poetry remove ...
  • uv: uv remove ...

Run

  • Poetry: poetry run ...
  • uv: uv run ...

Show outdated dependencies

  • Poetry: poetry show --outdated --top-level
  • uv: uv tree --outdated --depth=1

Add another index

Poetry has commands to add other indexes, but uv does not: you must edit pyproject.toml to do it.

  • Poetry: poetry source add $INDEX $URL

Poetry also has a command to do it globally but it’s unclear if it’s feasible with uv:

  • Poetry: poetry config repositories.$INDEX $URL

Publish a package

Poetry allows to build at the same time but uv requires a separate command:

  • Poetry: poetry publish --build --skip-existing [--repository $INDEX]
  • uv: uv build && uv publish [--index $INDEX]

We initially published this document in our internal wiki, but we thought it would be useful to others as well. Commands and options don’t always match, and it can be hard to get used to the new ones after a few years of Poetry usage.

The post Migrating commands from Poetry to uv appeared first on Bixoto Tech Blog.

]]>
Monitor anything with Telegraf, InfluxDB, Grafana and Python https://tech.bixoto.com/monitor-anything-with-telegraf-influxdb-grafana-and-python/ Wed, 07 Feb 2024 14:48:50 +0000 https://tech.bixoto.com/?p=40 At Bixoto we use the TIG stack to monitor our systems. “TIG” stands for “Telegraf, InfluxDB, Grafana”. Telegraf is an agent that collects metrics on your servers and sends it to InfluxDB, a database oriented for real-time data. Then, Grafana reads these data points and renders them with nice graphs and provides some alerting. Of … Read more

The post Monitor anything with Telegraf, InfluxDB, Grafana and Python appeared first on Bixoto Tech Blog.

]]>
At Bixoto we use the TIG stack to monitor our systems. “TIG” stands for “Telegraf, InfluxDB, Grafana”. Telegraf is an agent that collects metrics on your servers and sends it to InfluxDB, a database oriented for real-time data. Then, Grafana reads these data points and renders them with nice graphs and provides some alerting. Of course, this is not the only monitoring stack; you can use Telegraf without InfluxDB or Grafana, and Grafana without Telegraf or InfluxDB. There are already good tutorials on how to install and configure these three components so we won’t cover that here.

In this post we’ll focus on how to monitor anything with TIG and Python. We use Python here as an example, but what we’ll cover works with any programming language.

Let’s say we want to monitor how many orders we made in the past hour, both to see how it evolves over time and to send us an alert if it’s abnormally low. We’ll use PyMagento to query Magento orders and write the result in a format that Telegraf can read. Telegraf supports many inputs; here we’ll use a JSON object so we can add other metrics in the future.

Here is the code:

# monitor_magento_orders.py
import json
import sys
from collections import Counter
from datetime import datetime, timedelta

from magento import Magento, make_field_value_query, format_datetime


def main():
    # Create the Magento client
    client = Magento(token="...", base_url="https://bixoto.com")

    # Get the datetime of one hour ago
    utc_max_created_at = datetime.utcnow() - timedelta(hours=1)
    # Make the query to get orders created after this date
    query = make_field_value_query("created_at", format_datetime(utc_max_created_at), "gteq")

    # Initiate our counters
    counters = Counter()

    # Count orders
    for order in client.get_orders(query=query):
        counters["total_1h"] += 1

    # Write the counters as JSON on stdout with a key "metric": "magento_orders"
    json.dump({
        "metric": "magento_orders",
        **counters,
    }, sys.stdout)


if __name__ == '__main__':
    main()

If we run this code with python monitor_magento_orders.py, we get an output similar to this:

{"metric": "magento_orders", "total_1h": 43}

Good. Now let’s add the relevant configuration in Telegraf. Create a file /etc/telegraf/telegraf.d/magento_orders.conf with the following content:

[[inputs.exec]]
  interval = "10m"
  # Adjust with your own path
  commands = ["/usr/bin/python3 /home/myuser/monitor_magento_orders.py"]
  data_format = "json"
  json_name_key = "metric"

This adds a new Telegraf input that executes our Python code every 10 minutes and parses its result as JSON, assuming the key "metric" contains the name of the metric. Depending on what you’re monitoring, you may want to adjust this interval.

Reload Telegraf with sudo service telegraf reload, and wait a bit for the data to come.

Then open Grafana and create a new visualization using our metric magento_order with the field total_1h:

You can tweak the parameters, and the result should look like this:

That’s all. This is a simple visualization, but it can be extended with other measurements, for example orders with some specific attribute or status. Once you know how to make queries for visualizations, you can reuse them for alerts. There are already good resources on how to manage alert rules, so we won’t cover this here.

This short post showed how simple it is to monitor anything using TIG and Python: anything that can be measured by a Python program can be monitored. This is not even limited to Python; you can execute any program and Telegraf has a lot of built-in inputs.

The post Monitor anything with Telegraf, InfluxDB, Grafana and Python appeared first on Bixoto Tech Blog.

]]>
Always encode your Requests payloads in Python https://tech.bixoto.com/always-encode-your-requests-payloads-in-python/ Mon, 10 Jul 2023 06:34:08 +0000 https://tech2.bixoto.com/?p=21 At Bixoto, we use a lot of different APIs to interface with suppliers and other services. Today, I was working with an XML API using requests (via api_session) and xmltodict. TL;DR: use requests.post(url, data=my_string.encode("utf-8")) and not requests.post(url, data=my_string).Long version below: The simplified code looked like this: This worked great until I called client.hello() with a name that contained accents, such as “Élise”. The API … Read more

The post Always encode your Requests payloads in Python appeared first on Bixoto Tech Blog.

]]>
At Bixoto, we use a lot of different APIs to interface with suppliers and other services. Today, I was working with an XML API using requests (via api_session) and xmltodict.

TL;DR: use requests.post(url, data=my_string.encode("utf-8")) and not requests.post(url, data=my_string).
Long version below:

The simplified code looked like this:

from api_session import APISession
import xmltodict


class TheClient(APISession):
    def post_xml_api(self, path: str, payload: dict) -> dict:
        # Transform a dict into an XML string
        xml = xmltodict.unparse(payload)

        # POST it to the API
        response = self.post_api(
            path,
            data=xml,
            headers={"Content-Type": "application/xml; charset=utf-8"},
        )
        # Parse the response XML as a dict again
        response.encoding = response.apparent_encoding
        return xmltodict.parse(response.text)

    def hello(self, name: str) -> str:
        res = self.post_xml_api("/hello", {"name": name})
        return res["message"]


# ...
client = TheClient(base_url="...")
print(client.hello("John"))  # => "Hello John!"

This worked great until I called client.hello() with a name that contained accents, such as “Élise”. The API provider complained that it wasn’t receiving UTF-8 data.

To debug the API client, I set up a simple server using nc in another terminal:

nc -l 1234

Then I used it as my base URL:

# note: this is a feature of api_session, not requests
client = TheClient(base_url="http://localhost:1234")
client.hello("Élise")

This is the result request:

POST /hello HTTP/1.1
Host: localhost:1234
User-Agent: python-requests/2.31.0
...
Content-Type: application/xml; charset=utf-8
Content-Length: 57

<?xml version="1.0" encoding="utf-8"?>
<name>�lise</name>

There was indeed an issue with the encoding. I thought that Python used UTF-8 everywhere by default, but that’s not the case. The default charset for HTTP is ISO-8859-1, aka Latin-1 (see the RFC 2616).

Requests wraps Python’s http.client, which respects that:

If body is a string, it is encoded as ISO-8859-1, the default for HTTP.

The solution is to explicitly encode the request body:

# Before
response = requests.post(url, data=xml_string)
# After
response = requests.post(url, data=xml_string.encode("utf-8"))

That way, the body is already encoded and http.client doesn’t have to encode it by itself.

The post Always encode your Requests payloads in Python appeared first on Bixoto Tech Blog.

]]>