Skip to content
Open
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: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,11 @@ For AWS Braket:
```env
AETHER_DRIVER=aws
AWS_DEFAULT_REGION=us-east-1
AETHER_S3_BUCKET=your-bucket
AETHER_S3_BUCKET= # optional; leave blank to use Braket's default bucket
AETHER_DEVICE_ARN=arn:aws:braket:::device/quantum-simulator/amazon/sv1
```

`AETHER_S3_BUCKET` is required by the `aws` driver, together with the region and the device ARN: a missing or empty value throws an `InvalidDriverConfigException` on every call. Braket writes the task results to `s3://<bucket>/results`.
The `aws` driver needs the region and the device ARN; a missing or empty value for either throws an `InvalidDriverConfigException` on every call. `AETHER_S3_BUCKET` is optional: when set, Braket writes the task results to `s3://<bucket>/results`; when unset or blank, the SDK uses its own default bucket (`amazon-braket-<region>-<account-id>`), creating it on first use. That fallback needs `s3:CreateBucket` on the calling credentials in addition to the Braket and S3 object permissions; with a locked-down role, create the bucket yourself and set `AETHER_S3_BUCKET`.

See [Choosing a Driver](#choosing-a-driver) for a comparison of the available backends.

Expand Down
12 changes: 7 additions & 5 deletions bin/python/providers/aws.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,16 @@ def resolve_device(config: dict[str, Any]) -> Any:


def run_options(config: dict[str, Any]) -> dict[str, Any]:
"""Return the extra ``device.run()`` kwargs: the S3 destination folder.
"""Return the extra ``device.run()`` kwargs: the S3 destination folder, if any.

Raises:
ValueError: When *config* has no non-empty ``bucket``.
A configured ``bucket`` routes results to ``s3://<bucket>/results``.
Without one the kwargs stay empty and the SDK falls back to its default
bucket (``amazon-braket-<region>-<account>``, created on demand) and its
``tasks`` folder, exactly as ``AwsDevice.run()`` does on its own.
"""
bucket = config.get("bucket")
bucket = str(config.get("bucket") or "").strip()
if not bucket:
raise ValueError("Driver 'aws' requires a non-empty 'bucket' in driver_config.")
return {}

return {"s3_destination_folder": (bucket, "results")}

Expand Down
3 changes: 3 additions & 0 deletions config/aether.php
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,9 @@

'aws' => [
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
// Optional. Results go to s3://<bucket>/results when set; when unset or
// blank the Braket SDK uses its default bucket (amazon-braket-<region>-<account>),
// which it creates on first use and therefore needs s3:CreateBucket for.
'bucket' => env('AETHER_S3_BUCKET'),
'device_arn' => env('AETHER_DEVICE_ARN', 'arn:aws:braket:::device/quantum-simulator/amazon/sv1'),
'synchronous_safe' => true,
Expand Down
6 changes: 5 additions & 1 deletion src/Drivers/AwsBraketDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,15 @@ protected function driverName(): string
}

/**
* The S3 bucket is optional: without one the Braket SDK writes results to
* its default bucket, amazon-braket-<region>-<account>, which it creates on
* first use (so the credentials need s3:CreateBucket).
*
* @return list<string>
*/
protected function requiredConfig(): array
{
return ['region', 'device_arn', 'bucket'];
return ['region', 'device_arn'];
}

protected function beforeExecution(): void
Expand Down
27 changes: 26 additions & 1 deletion tests/Unit/Drivers/AwsBraketDriverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -221,10 +221,35 @@
} catch (InvalidDriverConfigException $e) {
expect($e->getMessage())->toContain('region');
expect($e->getMessage())->toContain('device_arn');
expect($e->getMessage())->toContain('bucket');
expect($e->getMessage())->not->toContain('bucket');
}
});

it('runs without a bucket and leaves the S3 destination to the SDK default', function (array $bucket) {
// config/aether.php yields null when AETHER_S3_BUCKET is unset and '' when the line is blank.
$driver = new AwsBraketDriver($this->bridge, [
'region' => 'us-east-1',
'device_arn' => 'arn:aws:braket:::device/quantum-simulator/amazon/sv1',
...$bucket,
]);

$circuit = $this->createMock(CircuitBuilder::class);
$circuit->method('qubitCount')->willReturn(1);
$circuit->method('shotCount')->willReturn(10);
$circuit->method('toArray')->willReturn(['qubits' => 1, 'gates' => [], 'shots' => 10]);

$this->bridge->expects($this->once())
->method('execute')
->with('circuit.py', $this->anything(), $this->anything())
->willReturn(['counts' => ['0' => 10]]);

expect($driver->executeCircuit($circuit))->toBeInstanceOf(CircuitResult::class);
})->with([
'key absent' => [[]],
'null from an unset env var' => [['bucket' => null]],
'empty string from a blank env line' => [['bucket' => '']],
]);

it('validates config on generateEntropy as well as executeCircuit', function () {
$driver = new AwsBraketDriver($this->bridge, ['region' => 'us-east-1', 'bucket' => 'test-bucket']);

Expand Down
18 changes: 12 additions & 6 deletions tests/python/test_providers.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,12 +138,18 @@ def test_returns_the_hook_result_as_a_dict(self):

assert provider_run_options(provider, {"tag": "abc"}) == {"tag": "abc"}

def test_aws_run_options_without_bucket_raises(self):
with pytest.raises(
ValueError,
match=r"^Driver 'aws' requires a non-empty 'bucket' in driver_config\.$",
):
provider_run_options(aws_provider, {})
def test_aws_run_options_without_bucket_lets_the_sdk_pick_its_default(self):
assert provider_run_options(aws_provider, {}) == {}

def test_aws_run_options_treats_a_blank_bucket_as_unset(self):
assert provider_run_options(aws_provider, {"bucket": ""}) == {}
assert provider_run_options(aws_provider, {"bucket": None}) == {}
assert provider_run_options(aws_provider, {"bucket": " "}) == {}

def test_aws_run_options_trims_the_bucket_name(self):
options = provider_run_options(aws_provider, {"bucket": " my-bucket "})

assert options == {"s3_destination_folder": ("my-bucket", "results")}

def test_aws_run_options_returns_the_s3_destination_folder(self):
options = provider_run_options(aws_provider, {"bucket": "my-bucket"})
Expand Down