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
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,7 @@ __pycache__/
# due to using tox and pytest
.tox
.cache
.ruff_cache

# generated protobuf stubs (make proto / make build)
amaas/grpc/protos/
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,24 @@
# CHANGELOG

## 1.5.0 - 2026-09-15

- Add `scan_reader` (sync and aio) for scanning a data source through a reader implementing
the new `amaas.grpc.reader.AMaasReader` protocol, mirroring the Go SDK's `ScanReader` and
`AmaasClientReader`. The SDK pulls only the chunks the scan engine requests, so remote
sources (e.g. S3 objects) need not be downloaded in full. `digest` defaults to `False`
because digest calculation reads the whole data source.
- Add an S3 object scan example under `examples/scan-s3obj` demonstrating a reader backed
by ranged S3 GETs.
- Add an Azure Blob scan example under `examples/scan-azureblob` demonstrating a reader backed
by ranged blob downloads, with SAS token or `DefaultAzureCredential` auth.
- Enforce the reader contract like the Go SDK: `read_bytes` is never called beyond `data_size`, and
a short read raises the new `MSG_ID_ERR_RETRIEVE_DATA` error instead of uploading truncated data
or hashing a truncated source for `digest=True`.
- Compute sha1 and sha256 digests in a single pass, halving the reads a remote reader makes when
`digest=True`.
- `scan_file` now reports `MSG_ID_ERR_UNEXPECTED_ERROR` for `OSError` other than permission-denied;
only `PermissionError` maps to `MSG_ID_ERR_FILE_NO_PERMISSION`.

## 1.4.8 - 2026-08-19

- Support new region ap-southeast-3 (Indonesia)
Expand Down
59 changes: 59 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,65 @@ AsyncIO Scan a file for malware and retrieves response data from the API.
**_Return_**
String the scanned result in JSON format.

### Scanning with a Reader

`scan_reader` scans a data source through a reader object you provide, mirroring the Go SDK's `AmaasClientReader` interface. The SDK pulls only the chunks the scan engine requests, so a reader backed by a remote source (for example an S3 object) never needs to download the whole object.

Implement the `amaas.grpc.reader.AMaasReader` protocol:

```python
class AMaasReader(Protocol):
def identifier(self) -> str:
"""Return the identifier of the data source, e.g. "s3://bucket/key"."""

def data_size(self) -> int:
"""Return the total size of the data source in bytes."""

def read_bytes(self, offset: int, length: int) -> bytes:
"""Return exactly length bytes of the data source starting at offset.
The SDK never requests beyond data_size; a short read raises
MSG_ID_ERR_RETRIEVE_DATA and fails the scan."""
```

Then pass the reader to `scan_reader` (or `amaas.grpc.aio.scan_reader`):

```python
reader = MyS3ObjectReader(bucket, key)
result = amaas.grpc.scan_reader(handle, reader, tags=tags)
```

See [examples/scan-s3obj/scan_s3obj.py](examples/scan-s3obj/scan_s3obj.py) for a complete S3 implementation.

#### `def amaas.grpc.scan_reader(handle: grpc.Channel, reader: AMaasReader, tags: List[str], pml: bool = False, feedback: bool = False, verbose: bool = False, digest: bool = False) -> str`

Scan a data source through an `AMaasReader` implementation.

**_Parameters_**

| Parameter | Description |
| --------- | ----------------------------------------------------------------------------------------------------------- |
| handle | The grpc Channel instance was created from the init function. |
| reader | An object implementing the `AMaasReader` protocol (identifier / data_size / read_bytes). |
| tags | A list of strings to be used to tag the scan result. At most 8 tags with a maximum length of 63 characters. |
| pml | Enable PML (Predictive Machine Learning) Detection. |
| feedback | Enable SPN feedback for Predictive Machine Learning Detection |
| verbose | Enable log verbose mode |
| digest | Calculate digests for cache search and result lookup. Defaults to `False` because digest calculation reads the whole data source; with a remote reader that defeats the purpose of partial reads. Pass `True` to opt in. |

**_Return_**
String the scanned result in JSON format.

#### `def amaas.grpc.aio.scan_reader(handle: grpc.aio.Channel, reader: AMaasReader, tags: List[str], pml: bool = False, feedback: bool = False, verbose: bool = False, digest: bool = False) -> str`

AsyncIO scan of a data source through an `AMaasReader` implementation. `read_bytes()` runs in a worker thread (`asyncio.to_thread`), so the event loop stays responsive while a remote reader (e.g. S3 ranged GETs) fetches a chunk. Readers stay synchronous, matching the sync client semantics.

**_Parameters_**

Same as `amaas.grpc.scan_reader`, with the aio Channel handle.

**_Return_**
String the scanned result in JSON format.

### Cleaning Up

#### `def amaas.grpc.quit(handle: grpc.aio.Channel) -> None`
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.4.8
1.5.0
Loading
Loading