Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions .bumpversion.cfg

This file was deleted.

124 changes: 48 additions & 76 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,99 +1,71 @@
name: CI

on:
push:
# Sequence of patterns matched against refs/tags
tags:
- 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
branches: [master]
pull_request:
release:
types: [published]
workflow_dispatch:

name: Release
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

jobs:
build:
name: Creating Windows and OSX wheels

build_wheels:
name: Wheels on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [macos-latest, windows-latest]
python-version: [3.6, 3.7, 3.8, 3.9]
os: [ubuntu-latest, ubuntu-24.04-arm, windows-latest, macos-14]

steps:
- uses: actions/checkout@v2

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- uses: actions/checkout@v7

- name: Install python dependencies
run: |
python -m pip install --upgrade pip
python -m pip install --upgrade setuptools
pip install wheel twine
pip install -e '.'
- name: Build and test wheels
uses: pypa/cibuildwheel@v4.1.0

- name: Publishing
env:
TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
run: |
python setup.py bdist_wheel
twine upload dist/*

build_manylinux2010:
name: Creating manylinux releases
- uses: actions/upload-artifact@v7
with:
name: wheels-${{ matrix.os }}
path: ./wheelhouse/*.whl

build_sdist:
name: Source distribution
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v7

- name: Build (manylinux2014)
uses: RalfG/python-wheels-manylinux-build@v0.3-manylinux2014_x86_64
with:
python-versions: 'cp36-cp36m cp37-cp37m cp38-cp38 cp39-cp39'
- name: Install uv
uses: astral-sh/setup-uv@v8.3.2
with:
enable-cache: true

- name: Set up Python
uses: actions/setup-python@v1
with:
python-version: 3.8
- name: Build sdist
run: uv build --sdist

- name: Install python dependencies
run: |
python -m pip install --upgrade pip
python -m pip install --upgrade setuptools
pip install wheel twine

- name: Publishing
env:
TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
run: |
twine upload dist/*-manylinux*.whl

sdist:
name: Creating source release
- uses: actions/upload-artifact@v7
with:
name: sdist
path: ./dist/*.tar.gz

publish:
name: Publish to PyPI
needs: [build_wheels, build_sdist]
runs-on: ubuntu-latest

if: github.event_name == 'release' && github.repository == 'TkTech/mutf8'
environment:
name: pypi
url: https://pypi.org/p/mutf8
permissions:
id-token: write
steps:
- uses: actions/checkout@v2

- name: Set up Python
uses: actions/setup-python@v1
with:
python-version: 3.8

- name: Install python dependencies
run: |
python -m pip install --upgrade pip
python -m pip install --upgrade setuptools
pip install wheel twine
- uses: actions/download-artifact@v8
with:
path: dist
merge-multiple: true

- name: Publishing
env:
TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
run: |
python setup.py sdist
twine upload dist/*
- name: Publish
uses: pypa/gh-action-pypi-publish@release/v1
39 changes: 0 additions & 39 deletions .github/workflows/test.yml

This file was deleted.

5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
*.pyc
*.pyd
venv
.venv/
.eggs
cython_debug/*
build/
_build/
dist/
wheelhouse/
*.egg-info
*.so
uv.lock
*.idea
65 changes: 39 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
![Tests](https://github.com/TkTech/mutf8/workflows/Tests/badge.svg?branch=master)
![CI](https://github.com/TkTech/mutf8/actions/workflows/release.yml/badge.svg)

# mutf-8

This package contains simple pure-python as well as C encoders and decoders for
the MUTF-8 character encoding. In most cases, you can also parse the even-rarer
CESU-8.
the MUTF-8 character encoding.

These days, you'll most likely encounter MUTF-8 when working on files or
protocols related to the JVM. Strings in a Java `.class` file are encoded using
Expand All @@ -14,26 +13,15 @@ serializer.
This library was extracted from [Lawu][], a Python library for working with JVM
class files.

## 🎉 Installation
## Installation

Prebuilt binary packages are available for most platforms and Python versions.
Install the package from PyPi:

```
pip install mutf8
```

Binary wheels are available for the following:

| | py3.6 | py3.7 | py3.8 | py3.9 |
| ---------------- | ----- | ----- | ----- | ----- |
| OS X (x86_64) | y | y | y | y |
| Windows (x86_64) | y | y | y | y |
| Linux (x86_64) | y | y | y | y |

If binary wheels are not available, it will attempt to build the C extension
from source with any C99 compiler. If it could not build, it will fall back
to a pure-python version.

## Usage

Encoding and decoding is simple:
Expand All @@ -48,23 +36,48 @@ bytes = encode_modified_utf8(unicode)
This module *does not* register itself globally as a codec, since importing
should be side-effect-free.

## 📈 Benchmarks
## Benchmarks

The C extension is significantly faster - often 20x to 40x faster.
The C extension is dramatically faster than the pure-Python fallback — from
~10× on tiny strings (where per-call overhead dominates) to several hundred
times on realistic payloads, and over 1000× when encoding large ASCII.

<!-- BENCHMARK START -->

### MUTF-8 Decoding
| Name | Min (μs) | Max (μs) | StdDev | Ops |
|------------------------------|------------|------------|----------|---------------|
| cmutf8-decode_modified_utf8 | 0.00009 | 0.00080 | 0.00000 | 9957678.56358 |
| pymutf8-decode_modified_utf8 | 0.00190 | 0.06040 | 0.00000 | 450455.96019 |

| Input | C | Python | Speedup |
|-------|---|--------|--------:|
| ASCII (16 B) | 81.5 ± 26 ns | 1.04 ± 0.18 µs | 13× |
| ASCII (1 KB) | 185 ± 38 ns | 65.7 ± 8.5 µs | 356× |
| ASCII (64 KB) | 5.58 ± 0.66 µs | 4.35 ± 0.23 ms | 780× |
| CJK (16 B) | 127 ± 15 ns | 1.33 ± 0.15 µs | 10× |
| CJK (1 KB) | 681 ± 117 ns | 99.3 ± 9.4 µs | 146× |
| CJK (64 KB) | 31.8 ± 3.3 µs | 6.28 ± 0.063 ms | 198× |
| Emoji (16 B) | 100 ± 21 ns | 882 ± 122 ns | 9× |
| Emoji (1 KB) | 538 ± 119 ns | 79.6 ± 6.9 µs | 148× |
| Emoji (64 KB) | 26.2 ± 4.1 µs | 5.32 ± 0.13 ms | 203× |
| Mixed (16 B) | 118 ± 27 ns | 2.17 ± 0.16 µs | 18× |
| Mixed (1 KB) | 860 ± 162 ns | 76.7 ± 9.2 µs | 89× |
| Mixed (64 KB) | 38 ± 5 µs | 5.3 ± 0.48 ms | 139× |

### MUTF-8 Encoding
| Name | Min (μs) | Max (μs) | StdDev | Ops |
|------------------------------|------------|------------|----------|----------------|
| cmutf8-encode_modified_utf8 | 0.00008 | 0.00151 | 0.00000 | 11897361.05101 |
| pymutf8-encode_modified_utf8 | 0.00180 | 0.16650 | 0.00000 | 474390.98091 |

| Input | C | Python | Speedup |
|-------|---|--------|--------:|
| ASCII (16 B) | 70.5 ± 5.8 ns | 1.23 ± 0.15 µs | 18× |
| ASCII (1 KB) | 109 ± 16 ns | 49 ± 6.2 µs | 449× |
| ASCII (64 KB) | 1.55 ± 0.27 µs | 2.93 ± 0.19 ms | 1889× |
| CJK (16 B) | 81.4 ± 12 ns | 2.02 ± 0.26 µs | 25× |
| CJK (1 KB) | 624 ± 127 ns | 108 ± 16 µs | 173× |
| CJK (64 KB) | 32.5 ± 2.9 µs | 6.8 ± 0.24 ms | 209× |
| Emoji (16 B) | 73 ± 10 ns | 1.17 ± 0.12 µs | 16× |
| Emoji (1 KB) | 621 ± 76 ns | 67 ± 8.5 µs | 108× |
| Emoji (64 KB) | 33.3 ± 6 µs | 3.98 ± 0.12 ms | 119× |
| Mixed (16 B) | 108 ± 97 ns | 2.48 ± 0.35 µs | 23× |
| Mixed (1 KB) | 1.39 ± 0.29 µs | 63.3 ± 3.5 µs | 45× |
| Mixed (64 KB) | 81.2 ± 14 µs | 3.91 ± 0.093 ms | 48× |

<!-- BENCHMARK END -->

## C Extension
Expand Down
2 changes: 2 additions & 0 deletions mutf8/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
for MUTF-8/CESU-8 into the python core.
"""

__version__ = "1.0.6"


try:
from mutf8.cmutf8 import decode_modified_utf8, encode_modified_utf8
Expand Down
Loading