Skip to content

Commit

Permalink
Use GCS support from Safir
Browse files Browse the repository at this point in the history
Replace the signed URL generation and the GCS mock for testing with
the corresponding code from Safir.
  • Loading branch information
rra committed Jan 12, 2023
1 parent 922f510 commit 44362bf
Show file tree
Hide file tree
Showing 11 changed files with 130 additions and 106 deletions.
6 changes: 3 additions & 3 deletions requirements/dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -227,9 +227,9 @@ httpx==0.23.3 \
# via
# -c requirements/main.txt
# -r requirements/dev.in
identify==2.5.12 \
--hash=sha256:0bc96b09c838310b6fcfcc61f78a981ea07f94836ef6ef553da5bb5d4745d662 \
--hash=sha256:e8a400c3062d980243d27ce10455a52832205649bbcaf27ffddb3dfaaf477bad
identify==2.5.13 \
--hash=sha256:8aa48ce56e38c28b6faa9f261075dea0a942dfbb42b341b4e711896cbb40f3f7 \
--hash=sha256:abb546bca6f470228785338a01b539de8a85bbf46491250ae03363956d8ebb10
# via pre-commit
idna==3.4 \
--hash=sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4 \
Expand Down
4 changes: 2 additions & 2 deletions requirements/main.in
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ google-auth
google-cloud-storage
jinja2
psycopg2
safir[db]
safir[db,gcs]
sqlalchemy[asyncio]
structlog

# Uncomment this, change the branch, comment out safir above, and run make
# update-deps-no-hashes to test against an unreleased version of Safir.
#git+https://github.com/lsst-sqre/safir@master#egg=safir[db]
#git+https://github.com/lsst-sqre/safir@master#egg=safir[db,gcs]
109 changes: 99 additions & 10 deletions requirements/main.txt
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,95 @@ certifi==2022.12.7 \
# httpcore
# httpx
# requests
charset-normalizer==2.1.1 \
--hash=sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845 \
--hash=sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f
charset-normalizer==3.0.1 \
--hash=sha256:00d3ffdaafe92a5dc603cb9bd5111aaa36dfa187c8285c543be562e61b755f6b \
--hash=sha256:024e606be3ed92216e2b6952ed859d86b4cfa52cd5bc5f050e7dc28f9b43ec42 \
--hash=sha256:0298eafff88c99982a4cf66ba2efa1128e4ddaca0b05eec4c456bbc7db691d8d \
--hash=sha256:02a51034802cbf38db3f89c66fb5d2ec57e6fe7ef2f4a44d070a593c3688667b \
--hash=sha256:083c8d17153ecb403e5e1eb76a7ef4babfc2c48d58899c98fcaa04833e7a2f9a \
--hash=sha256:0a11e971ed097d24c534c037d298ad32c6ce81a45736d31e0ff0ad37ab437d59 \
--hash=sha256:0bf2dae5291758b6f84cf923bfaa285632816007db0330002fa1de38bfcb7154 \
--hash=sha256:0c0a590235ccd933d9892c627dec5bc7511ce6ad6c1011fdf5b11363022746c1 \
--hash=sha256:0f438ae3532723fb6ead77e7c604be7c8374094ef4ee2c5e03a3a17f1fca256c \
--hash=sha256:109487860ef6a328f3eec66f2bf78b0b72400280d8f8ea05f69c51644ba6521a \
--hash=sha256:11b53acf2411c3b09e6af37e4b9005cba376c872503c8f28218c7243582df45d \
--hash=sha256:12db3b2c533c23ab812c2b25934f60383361f8a376ae272665f8e48b88e8e1c6 \
--hash=sha256:14e76c0f23218b8f46c4d87018ca2e441535aed3632ca134b10239dfb6dadd6b \
--hash=sha256:16a8663d6e281208d78806dbe14ee9903715361cf81f6d4309944e4d1e59ac5b \
--hash=sha256:292d5e8ba896bbfd6334b096e34bffb56161c81408d6d036a7dfa6929cff8783 \
--hash=sha256:2c03cc56021a4bd59be889c2b9257dae13bf55041a3372d3295416f86b295fb5 \
--hash=sha256:2e396d70bc4ef5325b72b593a72c8979999aa52fb8bcf03f701c1b03e1166918 \
--hash=sha256:2edb64ee7bf1ed524a1da60cdcd2e1f6e2b4f66ef7c077680739f1641f62f555 \
--hash=sha256:31a9ddf4718d10ae04d9b18801bd776693487cbb57d74cc3458a7673f6f34639 \
--hash=sha256:356541bf4381fa35856dafa6a965916e54bed415ad8a24ee6de6e37deccf2786 \
--hash=sha256:358a7c4cb8ba9b46c453b1dd8d9e431452d5249072e4f56cfda3149f6ab1405e \
--hash=sha256:37f8febc8ec50c14f3ec9637505f28e58d4f66752207ea177c1d67df25da5aed \
--hash=sha256:39049da0ffb96c8cbb65cbf5c5f3ca3168990adf3551bd1dee10c48fce8ae820 \
--hash=sha256:39cf9ed17fe3b1bc81f33c9ceb6ce67683ee7526e65fde1447c772afc54a1bb8 \
--hash=sha256:3ae1de54a77dc0d6d5fcf623290af4266412a7c4be0b1ff7444394f03f5c54e3 \
--hash=sha256:3b590df687e3c5ee0deef9fc8c547d81986d9a1b56073d82de008744452d6541 \
--hash=sha256:3e45867f1f2ab0711d60c6c71746ac53537f1684baa699f4f668d4c6f6ce8e14 \
--hash=sha256:3fc1c4a2ffd64890aebdb3f97e1278b0cc72579a08ca4de8cd2c04799a3a22be \
--hash=sha256:4457ea6774b5611f4bed5eaa5df55f70abde42364d498c5134b7ef4c6958e20e \
--hash=sha256:44ba614de5361b3e5278e1241fda3dc1838deed864b50a10d7ce92983797fa76 \
--hash=sha256:4a8fcf28c05c1f6d7e177a9a46a1c52798bfe2ad80681d275b10dcf317deaf0b \
--hash=sha256:4b0d02d7102dd0f997580b51edc4cebcf2ab6397a7edf89f1c73b586c614272c \
--hash=sha256:502218f52498a36d6bf5ea77081844017bf7982cdbe521ad85e64cabee1b608b \
--hash=sha256:503e65837c71b875ecdd733877d852adbc465bd82c768a067badd953bf1bc5a3 \
--hash=sha256:5995f0164fa7df59db4746112fec3f49c461dd6b31b841873443bdb077c13cfc \
--hash=sha256:59e5686dd847347e55dffcc191a96622f016bc0ad89105e24c14e0d6305acbc6 \
--hash=sha256:601f36512f9e28f029d9481bdaf8e89e5148ac5d89cffd3b05cd533eeb423b59 \
--hash=sha256:608862a7bf6957f2333fc54ab4399e405baad0163dc9f8d99cb236816db169d4 \
--hash=sha256:62595ab75873d50d57323a91dd03e6966eb79c41fa834b7a1661ed043b2d404d \
--hash=sha256:70990b9c51340e4044cfc394a81f614f3f90d41397104d226f21e66de668730d \
--hash=sha256:71140351489970dfe5e60fc621ada3e0f41104a5eddaca47a7acb3c1b851d6d3 \
--hash=sha256:72966d1b297c741541ca8cf1223ff262a6febe52481af742036a0b296e35fa5a \
--hash=sha256:74292fc76c905c0ef095fe11e188a32ebd03bc38f3f3e9bcb85e4e6db177b7ea \
--hash=sha256:761e8904c07ad053d285670f36dd94e1b6ab7f16ce62b9805c475b7aa1cffde6 \
--hash=sha256:772b87914ff1152b92a197ef4ea40efe27a378606c39446ded52c8f80f79702e \
--hash=sha256:79909e27e8e4fcc9db4addea88aa63f6423ebb171db091fb4373e3312cb6d603 \
--hash=sha256:7e189e2e1d3ed2f4aebabd2d5b0f931e883676e51c7624826e0a4e5fe8a0bf24 \
--hash=sha256:7eb33a30d75562222b64f569c642ff3dc6689e09adda43a082208397f016c39a \
--hash=sha256:81d6741ab457d14fdedc215516665050f3822d3e56508921cc7239f8c8e66a58 \
--hash=sha256:8499ca8f4502af841f68135133d8258f7b32a53a1d594aa98cc52013fff55678 \
--hash=sha256:84c3990934bae40ea69a82034912ffe5a62c60bbf6ec5bc9691419641d7d5c9a \
--hash=sha256:87701167f2a5c930b403e9756fab1d31d4d4da52856143b609e30a1ce7160f3c \
--hash=sha256:88600c72ef7587fe1708fd242b385b6ed4b8904976d5da0893e31df8b3480cb6 \
--hash=sha256:8ac7b6a045b814cf0c47f3623d21ebd88b3e8cf216a14790b455ea7ff0135d18 \
--hash=sha256:8b8af03d2e37866d023ad0ddea594edefc31e827fee64f8de5611a1dbc373174 \
--hash=sha256:8c7fe7afa480e3e82eed58e0ca89f751cd14d767638e2550c77a92a9e749c317 \
--hash=sha256:8eade758719add78ec36dc13201483f8e9b5d940329285edcd5f70c0a9edbd7f \
--hash=sha256:911d8a40b2bef5b8bbae2e36a0b103f142ac53557ab421dc16ac4aafee6f53dc \
--hash=sha256:93ad6d87ac18e2a90b0fe89df7c65263b9a99a0eb98f0a3d2e079f12a0735837 \
--hash=sha256:95dea361dd73757c6f1c0a1480ac499952c16ac83f7f5f4f84f0658a01b8ef41 \
--hash=sha256:9ab77acb98eba3fd2a85cd160851816bfce6871d944d885febf012713f06659c \
--hash=sha256:9cb3032517f1627cc012dbc80a8ec976ae76d93ea2b5feaa9d2a5b8882597579 \
--hash=sha256:9cf4e8ad252f7c38dd1f676b46514f92dc0ebeb0db5552f5f403509705e24753 \
--hash=sha256:9d9153257a3f70d5f69edf2325357251ed20f772b12e593f3b3377b5f78e7ef8 \
--hash=sha256:a152f5f33d64a6be73f1d30c9cc82dfc73cec6477ec268e7c6e4c7d23c2d2291 \
--hash=sha256:a16418ecf1329f71df119e8a65f3aa68004a3f9383821edcb20f0702934d8087 \
--hash=sha256:a60332922359f920193b1d4826953c507a877b523b2395ad7bc716ddd386d866 \
--hash=sha256:a8d0fc946c784ff7f7c3742310cc8a57c5c6dc31631269876a88b809dbeff3d3 \
--hash=sha256:ab5de034a886f616a5668aa5d098af2b5385ed70142090e2a31bcbd0af0fdb3d \
--hash=sha256:c22d3fe05ce11d3671297dc8973267daa0f938b93ec716e12e0f6dee81591dc1 \
--hash=sha256:c2ac1b08635a8cd4e0cbeaf6f5e922085908d48eb05d44c5ae9eabab148512ca \
--hash=sha256:c512accbd6ff0270939b9ac214b84fb5ada5f0409c44298361b2f5e13f9aed9e \
--hash=sha256:c75ffc45f25324e68ab238cb4b5c0a38cd1c3d7f1fb1f72b5541de469e2247db \
--hash=sha256:c95a03c79bbe30eec3ec2b7f076074f4281526724c8685a42872974ef4d36b72 \
--hash=sha256:cadaeaba78750d58d3cc6ac4d1fd867da6fc73c88156b7a3212a3cd4819d679d \
--hash=sha256:cd6056167405314a4dc3c173943f11249fa0f1b204f8b51ed4bde1a9cd1834dc \
--hash=sha256:db72b07027db150f468fbada4d85b3b2729a3db39178abf5c543b784c1254539 \
--hash=sha256:df2c707231459e8a4028eabcd3cfc827befd635b3ef72eada84ab13b52e1574d \
--hash=sha256:e62164b50f84e20601c1ff8eb55620d2ad25fb81b59e3cd776a1902527a788af \
--hash=sha256:e696f0dd336161fca9adbb846875d40752e6eba585843c768935ba5c9960722b \
--hash=sha256:eaa379fcd227ca235d04152ca6704c7cb55564116f8bc52545ff357628e10602 \
--hash=sha256:ebea339af930f8ca5d7a699b921106c6e29c617fe9606fa7baa043c1cdae326f \
--hash=sha256:f4c39b0e3eac288fedc2b43055cfc2ca7a60362d0e5e87a637beac5d801ef478 \
--hash=sha256:f5057856d21e7586765171eac8b9fc3f7d44ef39425f85dbcccb13b3ebea806c \
--hash=sha256:f6f45710b4459401609ebebdbcfb34515da4fc2aa886f95107f556ac69a9147e \
--hash=sha256:f97e83fa6c25693c7a35de154681fcc257c1c41b38beb0304b9c4d2d9e164479 \
--hash=sha256:f9d0c5c045a3ca9bedfc35dca8526798eb91a07aa7a2c0fee134c6c6f321cbd7 \
--hash=sha256:ff6f3db31555657f3163b15a6b7c6938d08df7adbfc9dd13d9d19edad678f1e8
# via requests
click==8.1.3 \
--hash=sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e \
Expand Down Expand Up @@ -133,14 +219,17 @@ google-auth==2.16.0 \
# google-api-core
# google-cloud-core
# google-cloud-storage
# safir
google-cloud-core==2.3.2 \
--hash=sha256:8417acf6466be2fa85123441696c4badda48db314c607cf1e5d543fa8bdc22fe \
--hash=sha256:b9529ee7047fd8d4bf4a2182de619154240df17fbe60ead399078c1ae152af9a
# via google-cloud-storage
google-cloud-storage==2.7.0 \
--hash=sha256:1ac2d58d2d693cb1341ebc48659a3527be778d9e2d8989697a2746025928ff17 \
--hash=sha256:f78a63525e72dd46406b255bbdf858a22c43d6bad8dc5bdeb7851a42967e95a1
# via -r requirements/main.in
# via
# -r requirements/main.in
# safir
google-crc32c==1.5.0 \
--hash=sha256:024894d9d3cfbc5943f8f230e23950cd4906b2fe004c72e29b209420a1e6b05a \
--hash=sha256:02c65b9817512edc6a4ae7c7e987fea799d2e0ee40c53ec573a692bee24de876 \
Expand Down Expand Up @@ -600,9 +689,9 @@ redis==4.4.2 \
--hash=sha256:a010f6cb7378065040a02839c3f75c7e0fb37a87116fb4a95be82a95552776c7 \
--hash=sha256:e6206448e2f8a432871d07d432c13ed6c2abcf6b74edb436c99752b1371be387
# via dramatiq
requests==2.28.1 \
--hash=sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983 \
--hash=sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349
requests==2.28.2 \
--hash=sha256:64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa \
--hash=sha256:98b1b2782e3c6c4904938b84c0eb932721069dfdb9134313beff7c83c2df24bf
# via
# google-api-core
# google-cloud-storage
Expand All @@ -614,9 +703,9 @@ rsa==4.9 \
--hash=sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7 \
--hash=sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21
# via google-auth
safir[db]==3.4.0 \
--hash=sha256:02379c630049b7f7f70f568f212090bbf5fb1dfd959a1b1e75dca85e515015e8 \
--hash=sha256:888ce44a83b45bc247f85f9eac1a8de8221758ea181bb483186b789339c9b29c
safir[db,gcs]==3.5.0 \
--hash=sha256:7e078d174379eaaf47ff8cb7d39e5d939dfd0afdf4389f5961a82da949921c0e \
--hash=sha256:b3a43ac1466b139142a84c5e931ca0a1780e5727430d4105911cecc14b68accf
# via -r requirements/main.in
six==1.16.0 \
--hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \
Expand Down
33 changes: 6 additions & 27 deletions src/vocutouts/uws/results.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,8 @@
from __future__ import annotations

from datetime import timedelta
from urllib.parse import urlparse

import google.auth
from google.auth import impersonated_credentials
from google.cloud import storage
from safir.gcs import SignedURLService

from .config import UWSConfig
from .models import JobResult, JobResultURL
Expand All @@ -31,8 +28,10 @@ class ResultStore:

def __init__(self, config: UWSConfig) -> None:
self._config = config
self._credentials, _ = google.auth.default()
self._gcs = storage.Client()
self._url_service = SignedURLService(
service_account=config.signing_service_account,
lifetime=timedelta(seconds=config.url_lifetime),
)

async def url_for_result(self, result: JobResult) -> JobResultURL:
"""Convert a job result into a signed URL.
Expand All @@ -50,27 +49,7 @@ async def url_for_result(self, result: JobResult) -> JobResultURL:
the lifetime has expired, which in turn will probably require a
longer-lived object to hold the credentials.
"""
uri = urlparse(result.url)
assert uri.scheme == "s3"
bucket = self._gcs.bucket(uri.netloc)
blob = bucket.blob(uri.path[1:])
signing_credentials = impersonated_credentials.Credentials(
source_credentials=self._credentials,
target_principal=self._config.signing_service_account,
target_scopes=(
"https://www.googleapis.com/auth/devstorage.read_only"
),
lifetime=2,
)
signed_url = blob.generate_signed_url(
version="v4",
expiration=timedelta(seconds=self._config.url_lifetime),
method="GET",
response_type=result.mime_type,
credentials=signing_credentials,
)

# Return the JobResultURL representation of this result.
signed_url = self._url_service.signed_url(result.url, result.mime_type)
return JobResultURL(
result_id=result.result_id,
url=signed_url,
Expand Down
11 changes: 6 additions & 5 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from __future__ import annotations

from datetime import datetime, timezone
from datetime import datetime, timedelta, timezone
from typing import Any, AsyncIterator, Dict, Iterator, List

import dramatiq
Expand All @@ -14,6 +14,7 @@
from fastapi import FastAPI
from httpx import AsyncClient
from safir.database import create_database_engine, initialize_database
from safir.testing.gcs import MockStorageClient, patch_google_storage

from vocutouts import main
from vocutouts.actors import job_started
Expand All @@ -24,8 +25,6 @@
from vocutouts.uws.schema import Base
from vocutouts.uws.utils import isodatetime

from .support.uws import mock_uws_google_storage


@dramatiq.actor(queue_name="cutout", store_results=True)
def cutout_test(
Expand Down Expand Up @@ -76,5 +75,7 @@ async def client(app: FastAPI) -> AsyncIterator[AsyncClient]:


@pytest.fixture(autouse=True)
def mock_google_storage() -> Iterator[None]:
yield from mock_uws_google_storage()
def mock_google_storage() -> Iterator[MockStorageClient]:
yield from patch_google_storage(
expected_expiration=timedelta(minutes=15), bucket_name="some-bucket"
)
2 changes: 1 addition & 1 deletion tests/handlers/async_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
<uws:parameter id="pos" isPost="true">CIRCLE 0.5 0.8 2</uws:parameter>
</uws:parameters>
<uws:results>
<uws:result id="cutout" xlink:href="https://example.com/cutout-result"\
<uws:result id="cutout" xlink:href="https://example.com/some/path"\
mime-type="application/fits"/>
</uws:results>
</uws:job>
Expand Down
4 changes: 2 additions & 2 deletions tests/handlers/sync_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ async def test_sync(client: AsyncClient) -> None:
params={"ID": "1:2:band:id", "Pos": "CIRCLE 0 -2 2"},
)
assert r.status_code == 303
assert r.headers["Location"] == "https://example.com/cutout-result"
assert r.headers["Location"] == "https://example.com/some/path"

# POST request.
r = await client.post(
Expand All @@ -33,7 +33,7 @@ async def test_sync(client: AsyncClient) -> None:
data={"ID": "3:4:band:id", "Pos": "CIRCLE 0 -2 2"},
)
assert r.status_code == 303
assert r.headers["Location"] == "https://example.com/cutout-result"
assert r.headers["Location"] == "https://example.com/some/path"
finally:
worker.stop()

Expand Down
52 changes: 2 additions & 50 deletions tests/support/uws.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@

import asyncio
import os
from datetime import datetime, timedelta, timezone
from typing import Any, Dict, Iterator, List, Optional
from unittest.mock import Mock, patch
from datetime import datetime, timezone
from typing import Any, Dict, List, Optional

import dramatiq
import structlog
Expand All @@ -15,7 +14,6 @@
from dramatiq.middleware import CurrentMessage, Middleware
from dramatiq.results import Results
from dramatiq.results.backends import StubBackend
from google.cloud import storage
from safir.database import create_sync_session
from sqlalchemy.future import select
from sqlalchemy.orm import scoped_session
Expand Down Expand Up @@ -154,52 +152,6 @@ def build_uws_config() -> UWSConfig:
)


class MockBlob(Mock):
def __init__(self) -> None:
super().__init__(spec=storage.blob.Blob)

def generate_signed_url(
self,
*,
version: str,
expiration: timedelta,
method: str,
response_type: str,
credentials: Any,
) -> str:
assert version == "v4"
assert expiration == timedelta(seconds=15 * 60)
assert method == "GET"
assert response_type == "application/fits"
return "https://example.com/cutout-result"


class MockBucket(Mock):
def __init__(self) -> None:
super().__init__(spec=storage.bucket.Bucket)

def blob(self, blob_name: str) -> Mock:
assert blob_name == "some/path"
return MockBlob()


class MockStorageClient(Mock):
def __init__(self) -> None:
super().__init__(spec=storage.Client)

def bucket(self, bucket_name: str) -> Mock:
assert bucket_name == "some-bucket"
return MockBucket()


def mock_uws_google_storage() -> Iterator[None]:
mock_gcs = MockStorageClient()
with patch("google.auth.impersonated_credentials.Credentials"):
with patch("google.auth.default", return_value=(None, None)):
with patch("google.cloud.storage.Client", return_value=mock_gcs):
yield


async def wait_for_job(job_service: JobService, user: str, job_id: str) -> Job:
"""Wait for a job that was just started and return it."""
job = await job_service.get(
Expand Down
9 changes: 6 additions & 3 deletions tests/uws/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

from __future__ import annotations

from datetime import timedelta
from typing import AsyncIterator, Iterator

import pytest
Expand All @@ -25,6 +26,7 @@
from safir.dependencies.http_client import http_client_dependency
from safir.middleware.ivoa import CaseInsensitiveQueryMiddleware
from safir.middleware.x_forwarded import XForwardedMiddleware
from safir.testing.gcs import MockStorageClient, patch_google_storage
from sqlalchemy.ext.asyncio import async_scoped_session
from structlog.stdlib import BoundLogger

Expand All @@ -38,7 +40,6 @@
TrivialPolicy,
WorkerSession,
build_uws_config,
mock_uws_google_storage,
trivial_job,
uws_broker,
)
Expand Down Expand Up @@ -98,8 +99,10 @@ def logger() -> BoundLogger:


@pytest.fixture(autouse=True)
def mock_google_storage() -> Iterator[None]:
yield from mock_uws_google_storage()
def mock_google_storage() -> Iterator[MockStorageClient]:
yield from patch_google_storage(
expected_expiration=timedelta(minutes=15), bucket_name="some-bucket"
)


@pytest_asyncio.fixture
Expand Down
Loading

0 comments on commit 44362bf

Please sign in to comment.