tena
tena provides a small async wrapper for text-to-image model calls.
Install
Prerequisites:
- Python 3.10+
- uv
Dependencies are declared in pyproject.toml:
openaigoogle-genaihttpxpytestpython-dotenv
Image generation
Python API
Use draw with a registered model_path. The function returns a list of
GeneratedImage objects. Each object contains image bytes and a MIME type.
import asyncio
from pathlib import Path
from tena import draw, suffix_for_mime_type
async def main() -> None:
images = await draw(
model_path="openrouter/gpt-image-2",
prompt="A clean product photo of a ceramic cup",
size="1024x1024",
number=1,
input_images=[
"./reference-1.png",
"https://example.com/reference-2.png",
],
)
image = images[0]
suffix = suffix_for_mime_type(image.mime_type)
Path(f"result{suffix}").write_bytes(image.data)
asyncio.run(main())
The package exports the reusable API from tena/__init__.py. tena/main.py is
only the CLI entrypoint; core image generation logic lives in tena/image.py.
CLI
The tena command is a thin wrapper around the Python API.
Generate one image and write it to a required output path:
uv run tena --model-path openrouter/gpt-image-2 --prompt "A clean product photo of a ceramic cup" --output ./result.png --size 1024x1024
The output path supports ~, including --output ~/Downloads/result.png and
--output=~/Downloads/result.png.
Use - as the prompt to read from stdin:
echo "A simple red cube on a clean white background" | uv run tena --model-path openrouter/gpt-image-2 --prompt - --output ./result.png --size 1024x1024
Generate multiple images with --number. The first image uses the exact output
path, and later images are numbered:
uv run tena --model-path zenmux/gpt-image-2 --prompt "A blue sphere" --output ./result.png --number 2 --size 1024x1024
This writes result.png and result-2.png.
Provide reference images with repeated --input-image arguments. Each value can
be a local file path or an http/https URL:
uv run tena \
--model-path openrouter/gpt-image-2 \
--prompt "Create a studio product photo using the reference objects" \
--input-image ./reference-1.png \
--input-image https://example.com/reference-2.png \
--output ./result.png \
--size 1024x1024
URL images are downloaded to /tmp/tena/input_images and cached by URL, so the
same URL is reused on later runs instead of downloaded again.
CLI logs are written to stderr and default to INFO. The generated output paths
are still written to stdout. Use --log-level WARNING to hide normal progress
logs, or --log-level DEBUG for diagnostic details:
uv run tena --model-path openrouter/gpt-image-2 --prompt "A blue sphere" --output ./result.png --log-level WARNING
INFO logs include concise request metadata, URL cache/download status, byte counts, and elapsed times. DEBUG logs include more detailed SDK and input image diagnostics. Logs do not include API keys, full prompts, full input URLs, or image data.
Return object
@dataclass(frozen=True)
class GeneratedImage:
data: bytes
mime_type: str
Registered models
Models are currently registered in code. model_path is the lookup key, and
model_realname is the model id sent to the upstream API.
@dataclass(frozen=True)
class ImageModel:
model_displayname: str
model_realname: str
client: Literal[
"openai",
"openrouter",
"gemini-interactions",
"gemini-generate-content",
]
api_key_env: str
base_url: str | None = None
default_mime_type: str = "image/png"
Current model paths:
302ai/gpt-image-2302ai/gemini-3.1-flash-image-previewgoogle/gemini-3.1-flash-imageopenrouter/gpt-image-2zenmux/gpt-image-2zenmux/gemini-3.1-flash-image
client means which upstream API contract is used. OpenAI-compatible gateways
can use client="openai" with a custom base_url. OpenRouter uses
client="openrouter" and calls its /images unified image generation endpoint
directly.
Gemini image models use two different API contracts:
gemini-interactionsuses Google's Interactions API and is used for direct Google Gemini access.gemini-generate-contentuses the Generate Content API and is used for Gemini-compatible gateways such as Zenmux and 302AI.
Environment variables
Set the API key required by the selected model entry. Values can be provided by
the process environment, a project-root .env file, or
~/.tena/config/.env:
OPENROUTER_API_KEYZENMUX_API_KEYAI302_API_KEYGEMINI_API_KEY
Dotenv files are loaded in this order: project-root .env, then
~/.tena/config/.env. Later values override earlier values.
Gemini size format
Gemini image models use a single size string that is parsed into
the Gemini image response configuration. Interactions requests use
response_format; Generate Content requests use image_config.
16:9->aspect_ratio2Kor4K->image_size16:9@2K-> bothaspect_ratioandimage_sizeauto-> no explicit Geminiresponse_format
OpenAI-compatible and OpenRouter clients pass size through as the API size
parameter.
Not implemented yet
The following parameters are part of the public function signature but are not implemented yet:
web_search
Passing web_search raises NotImplementedError.
Integration tests
Live image generation tests are grouped under tests/integration. Set the API
key for the model you want to test in the environment or in the project .env
file:
OPENROUTER_API_KEYforopenrouter/gpt-image-2ZENMUX_API_KEYforzenmux/gpt-image-2
Then run:
pytest tests/integration
Tests with missing API keys are skipped. When a test succeeds, it writes the
generated image to ~/Downloads with a tena-<provider>-gpt-image-2 filename
prefix.
Metadata
Release files for tena 0.1.8
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| tena-0.1.8.tar.gz | 72.4 kB | Without provenance | Details for tena-0.1.8.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| tena-0.1.8-py3-none-any.whl | Python 3 | none | any | Details for tena-0.1.8-py3-none-any.whl |
Total release size: 84.8 kB
Release files / tena-0.1.8.tar.gz
| Download URL | tena-0.1.8.tar.gz |
|---|---|
| Size | 72.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
22ce22d9f412632e63b1ba5e2aa8adf57430a727f476110d6c299c5bef0b3160
|
|
BLAKE2b-256 checksum How to use checksums |
a2abcfc783cf7559ed87d3d8fe75ae20f89506c7ae376be164dfc1bacc6ec95b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.8.13
|
Release files / tena-0.1.8-py3-none-any.whl
| Download URL | tena-0.1.8-py3-none-any.whl |
|---|---|
| Size | 12.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
b30297d1530c6a224d101e1921dc8e80e33d637949c91f1cf8a5630607b86b47
|
|
BLAKE2b-256 checksum How to use checksums |
e3a1bff8974cfe21a5915d3271f9d964d9d08189fbecbac5007fa0c5b0479e66
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.8.13
|
Security for tena 0.1.8
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.