Files
windmill/python-client
Ruben Fiszel 13b521651b fix(python-client): return at most size bytes from S3BufferedReader.read (#10623)
* feat: add unit tests for S3BufferedReader.read and improve read method implementation

* feat: refactor S3BufferedReader.read method and add unit tests for its functionality

* feat: implement peek() on S3BufferedReader with buffered reads

* fix(python-client): keep the read(size) contract and trim the test surface

Drop the duplicated `TestS3BufferedReaderRead` class from
`python-client/tests/wmill_client_test.py`: CI runs `pytest tests/` from
`python-client/wmill`, so that legacy manual harness never executes, and the
same assertions already live in `python-client/wmill/tests/test_s3_reader.py`.

Narrow that file to the four behaviours a future change could break, and make
the `bytes_generator` guard actually call `bytes_generator`.

Align `peek()` with `io.BufferedReader.peek`, which does at most one read on
the underlying stream, rather than looping until `size` bytes are buffered.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(python-client): hold read1 to one underlying read

read1 forwarded to read, so read1(-1) drained the whole object — the same
unbounded buffering this branch removes from read. Now that a buffer exists,
read1 can honour its own contract: fill only when the buffer is empty, then
serve from it.

Also treat read(None) as read(-1), per the BufferedReader contract, and pin
that read(0) does not pull from the stream: that holds only because the
drain sentinel is a negative size, and widening it to any falsy size would
reintroduce whole-file buffering.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(python-client): return from read1(0) without touching the stream

A zero-length read has nothing to serve, so pulling a chunk to satisfy it
both wastes a round trip and advances the stream. Guard it ahead of the
fill, and pin it with a chunk source that counts pulls.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Tushar <tusharanshu18@gmail.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-10 23:04:27 +02:00
..
2026-04-29 20:23:32 +00:00
2022-05-05 04:25:58 +02:00

wmill

The core client for the Windmill platform.

Usage

Basic Usage

The wmill package has several methods at the top-level for the most frequent operations you will need.

The following are some common examples:

import time

import wmill


def main():
    # Get the value of a variable
    wmill.get_variable("u/user/variable_path")
    
    # Run a script synchronously and get the result
    wmill.run_script("f/pathto/script", args={"arg1": "value1"})
    
    # Get the value of a resource
    wmill.get_resource("u/user/resource_path")
    
    # Set the script's state
    wmill.set_state({"ts": time.time()})
    
    # Get the script's state
    wmill.get_state()

Advanced Usage

The wmill package also exposes the Windmill class, which is the core client for the Windmill platform.

import time

from wmill import Windmill

def main():
    client = Windmill(
        # token=...  <- this is optional. otherwise the client will look for the WM_TOKEN env var
    )

    # Get the current version of the client
    client.version

    # Get the current user
    client.user
    
    # Convenience get and post methods exist for https://app.windmill.dev/openapi.html#/
    # these are thin wrappers around the httpx library's get and post methods
    # list worker groups
    client.get("/configs/list_worker_groups")
    # create a group
    client.post(
        f"/w/{client.workspace}/groups/create",
        json={
            "name": "my-group",
            "summary": "my group summary",
        }
    )
    
    # Get and set the state of the script
    now = time.time()
    client.state = {"ts": now}
    assert client.state == {"ts": now}
    
    # Run a job asynchronously
    job_id = client.run_script_async(path="path/to/script")
    # Get its status
    client.get_job_status(job_id)
    # Get its result
    client.get_result(job_id)