Skip to main content

afrim-py

Python bindings for the afrim ime engine.

Python Rust License Changelog

Repository

Built with 🦀🐍 by @esubaalew

About

afrim-py provides Python bindings for the powerful afrim input method engine, enabling developers to build sophisticated input method applications in Python. This project brings the capabilities of the Rust-based afrim engine to the Python ecosystem through PyO3 bindings.

🔋 Features Included

  • Preprocessor - Advanced key sequence processing and input transformation
  • Translator - Dictionary-based text translation with multiple candidates
  • TOML Support - Easy configuration through TOML files
  • Unicode Support - Full support for international characters
  • Rhai Scripting - Dynamic translation scripts (when rhai feature is enabled)
  • String Similarity - Fuzzy matching with strsim feature

Installation

afrim-py is available on pypi.

pip install afrim-py

Usage

Basic Example

from afrim_py import Preprocessor, Translator, Config

# Configure the preprocessor with key mappings
preprocessor_data = {
    "a1": "à",
    "e1": "é", 
    "u1": "ù",
    "hello": "hi"
}

# Configure the translator with dictionary
translator_dict = {
    "hello": ["hi", "hey", "greetings"],
    "world": ["earth", "globe", "planet"],
    "python": ["snake", "programming language"]
}

# Create instances
preprocessor = Preprocessor(preprocessor_data, buffer_size=64)
translator = Translator(translator_dict, auto_commit=True)

# Process keyboard events
changed = preprocessor.process("h", "keydown")
changed = preprocessor.process("e", "keydown") 
changed = preprocessor.process("l", "keydown")
changed = preprocessor.process("l", "keydown")
changed = preprocessor.process("o", "keydown")

# Get the processed input
current_input = preprocessor.get_input()  # "hello"

# Translate the input
translations = translator.translate(current_input)
print(translations)
# [{'texts': ['hi', 'hey', 'greetings'], 'code': 'hello', 'remaining_code': '', 'can_commit': True}]

# Process commands from the queue
while True:
    command = preprocessor.pop_queue()
    if command == "NOP":
        break
    print(f"Command: {command}")

Configuration

from afrim_py import Config
import json

# Configuration file `config.toml`
'''
[core]
buffer_size = 64
auto_capitalize = false
auto_commit = false
page_size = 10

[data]
a1 = "à"
e2 = "é"

[translators]
datetime = { path = "./scripts/datetime.toml" }

[translation]
hi = 'hello'
'''
config = Config('config.toml')

# Use the configuration
preprocessor_data = config.extract_data()
preprocessor = Preprocessor(preprocessor_data, 64)
translator_dict = config.extract_translation()
translator = Translator(translator_dict, True)

Advanced Usage with Command Processing

import asyncio
from afrim_py import Preprocessor, Translator, Config

class InputMethodEngine:
    def __init__(self, config_file: str):
        config = Config(config_file)
        self.preprocessor = Preprocessor(config.extract_data(), 64)
        self.translator = Translator(config.extract_translation(), True)
        self.running = False
    
    async def process_commands(self):
        """Process commands from the preprocessor queue"""
        while self.running:
            command = self.preprocessor.pop_queue()
            
            if command == "NOP":
                await asyncio.sleep(0.01)  # Small delay
                continue
                
            # Handle different command types
            if isinstance(command, dict):
                if "Insert" in command:
                    text = command["Insert"]["text"]
                    print(f"Insert: {text}")
                elif "Delete" in command:
                    count = command["Delete"]["count"]
                    print(f"Delete: {count} characters")
            else:
                print(f"Command: {command}")
    
    def handle_key_event(self, key, state="keydown"):
        """Handle keyboard events"""
        changed = self.preprocessor.process(key, state)
        
        if changed:
            current_input = self.preprocessor.get_input()
            if current_input:
                translations = self.translator.translate(current_input)
                return translations
        return []
    
    def commit_text(self, text):
        """Commit selected text"""
        self.preprocessor.commit(text)
    
    async def start(self):
        """Start the input method engine"""
        self.running = True
        await self.process_commands()
    
    def stop(self):
        """Stop the input method engine"""
        self.running = False

# Usage
async def main():
    ime = InputMethodEngine(
        preprocessor_data={"A": "ዕ", "Aa": "ዓ", "C": "ጭ"},
        translator_dict={"Atarah": ["ዓጣራ"], "Adiel": ["ዓዲዔል"]}
    )
    
    # Simulate key events
    translations = ime.handle_key_event("A")
    translations = ime.handle_key_event("a")
    translations = ime.handle_key_event("C")
    
    print("Translations:", translations)
    
    # Commit text
    if translations:
        ime.commit_text(translations[0]["texts"][0])

# Run the example
# asyncio.run(main())

Development

Build requirements

  • Rust 1.70+

  • Cargo

  • Python 3.8+ and and maturin

  • uv (optional)

Build from source

To simplify the development, we recommend to use uv.

Using maturin

# Clone the repository
git clone https://github.com/fodydev/afrim-py.git
cd afrim-py

# Create virtual environment
python -m venv .venv
source .venv/bin/activate  # On Windows: .venv\Scripts\activate

# Development build
maturin develop

# Release build
maturin build --release

# Build wheel
maturin build --interpreter python

Using uv

# Clone the repository
git clone https://github.com/fodydev/afrim-py.git
cd afrim-py

# Prerelease build
uv build --prerelease

# Release build
uv build

Testing

The project includes tests that represent a real user scenario:

# Run all tests
python -m pytest tests/ -v

Contributing

Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.

Acknowledgments

  • afrim-js - Web bindings that inspired this project

License

Licensed under the MIT LICENSE.

Metadata

Release files for afrim-py 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for afrim-py 0.1.0
File Size Uploaded
afrim_py-0.1.0.tar.gz 32.2 kB Details

Release files / afrim_py-0.1.0.tar.gz

Download URL afrim_py-0.1.0.tar.gz
Size 32.2 kB
Tags Source
SHA-256 checksum
How to use checksums
e0adb85d661feb056a0fbf3fbd79a969c43bac8bd5fa2ca1bb1225173128e1cc
BLAKE2b-256 checksum
How to use checksums
7dfa623368f23e08596e753b1c708096cff57c2aadc11de768b4d2a95dc11041
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.9.30 {"installer":{"name":"uv","version":"0.9.30","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":null,"id":"forky","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release history Release notifications | RSS feed

This release

0.1.0 This release

1 release file

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page