Create STAC Catalogs With PyStac#

This notebook provides a comprehensive guide to creating a STAC (SpatioTemporal Asset Catalog) catalog using the pystac library. It demonstrates how to process geospatial imagery, generate metadata, and organize the data into a STAC-compliant catalog. The catalog is saved locally and optionally uploaded to an S3 bucket for public access.

Key Steps#

  1. Import Required Libraries The notebook begins by importing essential libraries such as pystac, rasterio, shapely, and boto3 for geospatial processing, metadata creation, and cloud storage interaction.

  2. Load Environment Variables Environment variables for S3 credentials and endpoints are loaded using the dotenv library to securely access cloud storage.

  3. Define S3 Interaction Functions Functions are defined to:

  • Initialize an S3 client.

  • Upload files to an S3 bucket. These functions handle file uploads and provide public URLs for the uploaded assets.

  1. Create a STAC Catalog A new STAC catalog is created using pystac.Catalog. This catalog serves as the root for organizing geospatial assets.

  2. Generate Metadata for Geospatial Images Functions are provided to:

  • Extract bounding boxes and footprints from raster files.

  • Parse timestamps from filenames. These metadata are used to create STAC items for each geospatial image.

  1. Add Items to the Catalog The notebook iterates over a list of Cloud Optimized GeoTIFF (COG) files, generates metadata for each file, and adds them as items to the catalog. Each item includes an asset pointing to the image file.

  2. Save and Upload the Catalog The catalog is saved locally in a self-contained format. Optionally, the catalog and its assets can be uploaded to an S3 bucket for public access.

  3. Connect to the Catalog The notebook demonstrates how to connect to the uploaded catalog using pystac-client and browse its contents.

  4. Explore the Catalog Using STAC Browser Instructions are provided to explore the catalog using the STAC Browser. Additional code is included to configure CORS settings for the S3 bucket if needed.

Import the required libraries#

import os
import glob
import json
import rasterio
from rasterio.warp import transform_bounds
import pystac
import pystac_client
from datetime import datetime, timezone
from shapely.geometry import Polygon, mapping
from pyproj import Transformer
from dotenv import load_dotenv
import logging
import boto3
from boto3.s3.transfer import TransferConfig
from botocore.exceptions import ClientError
load_dotenv()
True

Get environment variables#

S3_END_POINT = os.getenv('S3_END_POINT')
S3_BUCKET_NAME = os.getenv('BUCKET_NAME')
S3_ACCESS_KEY = os.getenv('S3_ACCESS_KEY')
S3_SECRET_ACCESS_KEY = os.getenv('S3_SECRET_ACCESS_KEY')
print (S3_END_POINT)
print (S3_BUCKET_NAME)
https://object-store.os-api.cci2.ecmwf.int
meditwin-training

Functions to upload files to S3#

os.environ["AWS_REQUEST_CHECKSUM_CALCULATION"]="when_required"
os.environ["AWS_RESPONSE_CHECKSUM_VALIDATION"] = "when_required"
config = TransferConfig(multipart_threshold=1024 * 1024 * 100)
def initiate_s3_client(end_point: str, access_key: str, secret_access_key: str):
    s3_client = boto3.client(
        "s3",
        endpoint_url=end_point,
        aws_access_key_id=access_key,
        aws_secret_access_key=secret_access_key,
    )
    return s3_client
def upload_file_s3(file_name: str, bucket_name: str, prefix: str):
    s3_client = initiate_s3_client(S3_END_POINT, S3_ACCESS_KEY, S3_SECRET_ACCESS_KEY)
    object_name = os.path.basename(file_name)
    try:
        s3_client.upload_file(
            Filename=file_name,
            Bucket=bucket_name,
            Key="{0}/{1}".format(prefix, object_name),  # pylint:disable=C0209
            ExtraArgs={"ACL": "public-read"},
            Config=config,
        )
    except ClientError as exce:
        logging.error(exce)
        return False, None
    return True, os.path.join("s3://", bucket_name, prefix, object_name)

Create STAC catalog#

catalog = pystac.Catalog(id='meditwin-catalog', description='This catalog is a basic demonstration catalog utilizing TCI Sentinel-2 images.', title="MediTwin Catalog Created by ECMWF for Demo")

List children inside the catalog#

print(list(catalog.get_children()))
print(list(catalog.get_items()))
[]
[]
print(json.dumps(catalog.to_dict(), indent=4))
{
    "type": "Catalog",
    "id": "meditwin-catalog",
    "stac_version": "1.1.0",
    "description": "This catalog is a basic demonstration catalog utilizing TCI Sentinel-2 images.",
    "links": [],
    "title": "MediTwin Catalog Created by ECMWF for Demo"
}

Create a STAC collection and add it to the catalog#

collection = pystac.Collection(id="sentinel-2-l2a-cog",
                                description="Sentinel-2 L2A COG Collection",
                                title="Sentinel-2 L2A COG",
                                extent=pystac.Extent(
                                    spatial=pystac.SpatialExtent([[-180, -90, 180, 90]]),
                                    temporal=pystac.TemporalExtent([[datetime(2015, 6, 23, tzinfo=timezone.utc), datetime.now(timezone.utc)]]),
                                ),)
catalog.add_child(collection)
print(list(catalog.get_children()))
print(list(catalog.get_items()))
[<Collection id=sentinel-2-l2a-cog>]
[]
print(json.dumps(catalog.to_dict(), indent=4))
{
    "type": "Catalog",
    "id": "meditwin-catalog",
    "stac_version": "1.1.0",
    "description": "This catalog is a basic demonstration catalog utilizing TCI Sentinel-2 images.",
    "links": [
        {
            "rel": "child",
            "href": null,
            "type": "application/json",
            "title": "Sentinel-2 L2A COG"
        }
    ],
    "title": "MediTwin Catalog Created by ECMWF for Demo"
}

Functions to create boundig box and get timestamp from images#

def get_bbox_and_footprint(raster):
    with rasterio.open(raster) as r:
        src_crs = r.crs
        bounds = r.bounds

        # Transform the bounding box to EPSG:4326
        bbox_4326 = transform_bounds(src_crs, "EPSG:4326", bounds.left, bounds.bottom, bounds.right, bounds.top)
        bbox = [bbox_4326[0], bbox_4326[1], bbox_4326[2], bbox_4326[3]]

        # Transform corner points individually for footprint
        transformer = Transformer.from_crs(src_crs, "EPSG:4326", always_xy=True)
        bottom_left = transformer.transform(bounds.left, bounds.bottom)
        top_left = transformer.transform(bounds.left, bounds.top)
        top_right = transformer.transform(bounds.right, bounds.top)
        bottom_right = transformer.transform(bounds.right, bounds.bottom)

        footprint = Polygon([
            bottom_left,
            top_left,
            top_right,
            bottom_right,
            bottom_left  # close the polygon
        ])

        return bbox, mapping(footprint)
import re
def extract_datetime_from_name(name: str) -> datetime:
    match = re.search(r"_(\d{8}T\d{6})_", name)
    if match:
        dt_str = match.group(1)
        dt = datetime.strptime(dt_str, "%Y%m%dT%H%M%S")
        return dt.replace(tzinfo=timezone.utc)
    else:
        raise ValueError("No datetime found in filename")

List images to be added as items to STAC catalog#

list_cog_files = glob.glob("../data/*_cog.tif")
print (list_cog_files)
['../data/T34SFG_20240621T092031_TCI_10m_cog.tif', '../data/T34SFF_20220209T091131_TCI_10m_cog.tif', '../data/T34SEG_20240601T092031_TCI_10m_cog.tif', '../data/T34SGH_20240608T090601_TCI_10m_cog.tif']

Iterate over the images and create an item for each one#

for cog_file in list_cog_files:
    bbox,footprint = get_bbox_and_footprint(cog_file)
    print (bbox)
    print (footprint)
    img_name = os.path.basename(cog_file)
    print (img_name)
    img_date = extract_datetime_from_name(img_name)
    print (img_date)
    item = pystac.Item(id=img_name.split(".")[0],
                 geometry=footprint,
                 bbox=bbox,
                 datetime=img_date,
                 collection = "sentinel-2-l2a-cog",
                 properties={})
    collection.add_item(item)
    remote_path = f"{S3_END_POINT}/{S3_BUCKET_NAME}/mohanad/cog-data/"+img_name
    print (remote_path)
    item.add_asset(
        key='tci',
        asset=pystac.Asset(
            href=remote_path,
            media_type=pystac.MediaType.GEOTIFF
        )
    )
[22.123180314837523, 36.934510752784284, 23.386942157546073, 37.94208531976598]
{'type': 'Polygon', 'coordinates': (((22.123180314837523, 36.95256912206579), (22.13807393193165, 37.94208531976598), (23.386942157546073, 37.923373495036124), (23.35573236945194, 36.934510752784284), (22.123180314837523, 36.95256912206579)),)}
T34SFG_20240621T092031_TCI_10m_cog.tif
2024-06-21 09:20:31+00:00
https://object-store.os-api.cci2.ecmwf.int/meditwin-training/mohanad/cog-data/T34SFG_20240621T092031_TCI_10m_cog.tif
[22.110239691315645, 36.03410504840171, 23.35846754627108, 37.041254047568394]
{'type': 'Polygon', 'coordinates': (((22.110239691315645, 36.05158282566385), (22.124485547335958, 37.041254047568394), (23.35846754627108, 37.02313780213882), (23.328614229465078, 36.03410504840171), (22.110239691315645, 36.05158282566385)),)}
T34SFF_20220209T091131_TCI_10m_cog.tif
2022-02-09 09:11:31+00:00
https://object-store.os-api.cci2.ecmwf.int/meditwin-training/mohanad/cog-data/T34SFF_20220209T091131_TCI_10m_cog.tif
[20.999772364545226, 36.95147947140662, 22.249354291576726, 37.947589571562965]
{'type': 'Polygon', 'coordinates': (((20.9997753443075, 36.957881094777285), (20.999772364545226, 37.947589571562965), (22.249354291576726, 37.94095623014448), (22.233005248219218, 36.95147947140662), (20.9997753443075, 36.957881094777285)),)}
T34SEG_20240601T092031_TCI_10m_cog.tif
2024-06-01 09:20:31+00:00
https://object-store.os-api.cci2.ecmwf.int/meditwin-training/mohanad/cog-data/T34SEG_20240601T092031_TCI_10m_cog.tif
[23.272364192591976, 37.80685703224135, 24.566452796700293, 38.826281708269896]
{'type': 'Polygon', 'coordinates': (((23.272364192591976, 37.83751218527424), (23.303470403104747, 38.826281708269896), (24.566452796700293, 38.79452723963919), (24.51836223999805, 37.80685703224135), (23.272364192591976, 37.83751218527424)),)}
T34SGH_20240608T090601_TCI_10m_cog.tif
2024-06-08 09:06:01+00:00
https://object-store.os-api.cci2.ecmwf.int/meditwin-training/mohanad/cog-data/T34SGH_20240608T090601_TCI_10m_cog.tif

Describe the catalog#

catalog
catalog.describe()
* <Catalog id=meditwin-catalog>
    * <Collection id=sentinel-2-l2a-cog>
      * <Item id=T34SFG_20240621T092031_TCI_10m_cog>
      * <Item id=T34SFF_20220209T091131_TCI_10m_cog>
      * <Item id=T34SEG_20240601T092031_TCI_10m_cog>
      * <Item id=T34SGH_20240608T090601_TCI_10m_cog>

Normalize the hrefs#

catalog.normalize_hrefs(f"{S3_END_POINT}/{S3_BUCKET_NAME}/mohanad/meditwin-catalog/stac")
catalog.describe()
* <Catalog id=meditwin-catalog>
    * <Collection id=sentinel-2-l2a-cog>
      * <Item id=T34SFG_20240621T092031_TCI_10m_cog>
      * <Item id=T34SFF_20220209T091131_TCI_10m_cog>
      * <Item id=T34SEG_20240601T092031_TCI_10m_cog>
      * <Item id=T34SGH_20240608T090601_TCI_10m_cog>
print("Catalog HREF: ", catalog.get_self_href())
print("Collection HREF: ", collection.get_self_href())
print("Item HREF: ", item.get_self_href())
Catalog HREF:  https://object-store.os-api.cci2.ecmwf.int/meditwin-training/mohanad/meditwin-catalog/stac/catalog.json
Collection HREF:  https://object-store.os-api.cci2.ecmwf.int/meditwin-training/mohanad/meditwin-catalog/stac/sentinel-2-l2a-cog/collection.json
Item HREF:  https://object-store.os-api.cci2.ecmwf.int/meditwin-training/mohanad/meditwin-catalog/stac/sentinel-2-l2a-cog/T34SGH_20240608T090601_TCI_10m_cog/T34SGH_20240608T090601_TCI_10m_cog.json

Save the catalog to a local directory#

catalog.save(catalog_type=pystac.CatalogType.ABSOLUTE_PUBLISHED,dest_href="../data/meditwin-catalog/stac")

Upload the catalog to S3#

for root, dirs, files in os.walk("../data/meditwin-catalog/stac", topdown=False):
   for file in files:
      full_path = os.path.join(root, file)
      print(full_path)
      rel_path = os.path.relpath(full_path, start="../data")
      print(rel_path)
      prefix = os.path.dirname(rel_path)
      print(prefix)
      file_name = os.path.basename(full_path)
      print (file_name)
      success, s3_path = upload_file_s3(full_path, S3_BUCKET_NAME, "mohanad/"+prefix)
      if success:
        print(f"File uploaded successfully to {s3_path}")
        print (f"File public URL: {s3_path.replace('s3://', S3_END_POINT + '/')}")

print("Upload completed.")
../data/meditwin-catalog/stac/T34SEG_20240601T092031_TCI_10m_cog/T34SEG_20240601T092031_TCI_10m_cog.json
meditwin-catalog/stac/T34SEG_20240601T092031_TCI_10m_cog/T34SEG_20240601T092031_TCI_10m_cog.json
meditwin-catalog/stac/T34SEG_20240601T092031_TCI_10m_cog
T34SEG_20240601T092031_TCI_10m_cog.json
File uploaded successfully to s3://meditwin-training/mohanad/meditwin-catalog/stac/T34SEG_20240601T092031_TCI_10m_cog/T34SEG_20240601T092031_TCI_10m_cog.json
File public URL: https://object-store.os-api.cci2.ecmwf.int/meditwin-training/mohanad/meditwin-catalog/stac/T34SEG_20240601T092031_TCI_10m_cog/T34SEG_20240601T092031_TCI_10m_cog.json
../data/meditwin-catalog/stac/T34SGH_20240608T090601_TCI_10m_cog/T34SGH_20240608T090601_TCI_10m_cog.json
meditwin-catalog/stac/T34SGH_20240608T090601_TCI_10m_cog/T34SGH_20240608T090601_TCI_10m_cog.json
meditwin-catalog/stac/T34SGH_20240608T090601_TCI_10m_cog
T34SGH_20240608T090601_TCI_10m_cog.json
File uploaded successfully to s3://meditwin-training/mohanad/meditwin-catalog/stac/T34SGH_20240608T090601_TCI_10m_cog/T34SGH_20240608T090601_TCI_10m_cog.json
File public URL: https://object-store.os-api.cci2.ecmwf.int/meditwin-training/mohanad/meditwin-catalog/stac/T34SGH_20240608T090601_TCI_10m_cog/T34SGH_20240608T090601_TCI_10m_cog.json
../data/meditwin-catalog/stac/T34SFG_20240621T092031_TCI_10m_cog/T34SFG_20240621T092031_TCI_10m_cog.json
meditwin-catalog/stac/T34SFG_20240621T092031_TCI_10m_cog/T34SFG_20240621T092031_TCI_10m_cog.json
meditwin-catalog/stac/T34SFG_20240621T092031_TCI_10m_cog
T34SFG_20240621T092031_TCI_10m_cog.json
File uploaded successfully to s3://meditwin-training/mohanad/meditwin-catalog/stac/T34SFG_20240621T092031_TCI_10m_cog/T34SFG_20240621T092031_TCI_10m_cog.json
File public URL: https://object-store.os-api.cci2.ecmwf.int/meditwin-training/mohanad/meditwin-catalog/stac/T34SFG_20240621T092031_TCI_10m_cog/T34SFG_20240621T092031_TCI_10m_cog.json
../data/meditwin-catalog/stac/sentinel-2-l2a-cog/T34SEG_20240601T092031_TCI_10m_cog/T34SEG_20240601T092031_TCI_10m_cog.json
meditwin-catalog/stac/sentinel-2-l2a-cog/T34SEG_20240601T092031_TCI_10m_cog/T34SEG_20240601T092031_TCI_10m_cog.json
meditwin-catalog/stac/sentinel-2-l2a-cog/T34SEG_20240601T092031_TCI_10m_cog
T34SEG_20240601T092031_TCI_10m_cog.json
File uploaded successfully to s3://meditwin-training/mohanad/meditwin-catalog/stac/sentinel-2-l2a-cog/T34SEG_20240601T092031_TCI_10m_cog/T34SEG_20240601T092031_TCI_10m_cog.json
File public URL: https://object-store.os-api.cci2.ecmwf.int/meditwin-training/mohanad/meditwin-catalog/stac/sentinel-2-l2a-cog/T34SEG_20240601T092031_TCI_10m_cog/T34SEG_20240601T092031_TCI_10m_cog.json
../data/meditwin-catalog/stac/sentinel-2-l2a-cog/T34SGH_20240608T090601_TCI_10m_cog/T34SGH_20240608T090601_TCI_10m_cog.json
meditwin-catalog/stac/sentinel-2-l2a-cog/T34SGH_20240608T090601_TCI_10m_cog/T34SGH_20240608T090601_TCI_10m_cog.json
meditwin-catalog/stac/sentinel-2-l2a-cog/T34SGH_20240608T090601_TCI_10m_cog
T34SGH_20240608T090601_TCI_10m_cog.json
File uploaded successfully to s3://meditwin-training/mohanad/meditwin-catalog/stac/sentinel-2-l2a-cog/T34SGH_20240608T090601_TCI_10m_cog/T34SGH_20240608T090601_TCI_10m_cog.json
File public URL: https://object-store.os-api.cci2.ecmwf.int/meditwin-training/mohanad/meditwin-catalog/stac/sentinel-2-l2a-cog/T34SGH_20240608T090601_TCI_10m_cog/T34SGH_20240608T090601_TCI_10m_cog.json
../data/meditwin-catalog/stac/sentinel-2-l2a-cog/T34SFG_20240621T092031_TCI_10m_cog/T34SFG_20240621T092031_TCI_10m_cog.json
meditwin-catalog/stac/sentinel-2-l2a-cog/T34SFG_20240621T092031_TCI_10m_cog/T34SFG_20240621T092031_TCI_10m_cog.json
meditwin-catalog/stac/sentinel-2-l2a-cog/T34SFG_20240621T092031_TCI_10m_cog
T34SFG_20240621T092031_TCI_10m_cog.json
File uploaded successfully to s3://meditwin-training/mohanad/meditwin-catalog/stac/sentinel-2-l2a-cog/T34SFG_20240621T092031_TCI_10m_cog/T34SFG_20240621T092031_TCI_10m_cog.json
File public URL: https://object-store.os-api.cci2.ecmwf.int/meditwin-training/mohanad/meditwin-catalog/stac/sentinel-2-l2a-cog/T34SFG_20240621T092031_TCI_10m_cog/T34SFG_20240621T092031_TCI_10m_cog.json
../data/meditwin-catalog/stac/sentinel-2-l2a-cog/T34SFF_20220209T091131_TCI_10m_cog/T34SFF_20220209T091131_TCI_10m_cog.json
meditwin-catalog/stac/sentinel-2-l2a-cog/T34SFF_20220209T091131_TCI_10m_cog/T34SFF_20220209T091131_TCI_10m_cog.json
meditwin-catalog/stac/sentinel-2-l2a-cog/T34SFF_20220209T091131_TCI_10m_cog
T34SFF_20220209T091131_TCI_10m_cog.json
File uploaded successfully to s3://meditwin-training/mohanad/meditwin-catalog/stac/sentinel-2-l2a-cog/T34SFF_20220209T091131_TCI_10m_cog/T34SFF_20220209T091131_TCI_10m_cog.json
File public URL: https://object-store.os-api.cci2.ecmwf.int/meditwin-training/mohanad/meditwin-catalog/stac/sentinel-2-l2a-cog/T34SFF_20220209T091131_TCI_10m_cog/T34SFF_20220209T091131_TCI_10m_cog.json
../data/meditwin-catalog/stac/sentinel-2-l2a-cog/collection.json
meditwin-catalog/stac/sentinel-2-l2a-cog/collection.json
meditwin-catalog/stac/sentinel-2-l2a-cog
collection.json
File uploaded successfully to s3://meditwin-training/mohanad/meditwin-catalog/stac/sentinel-2-l2a-cog/collection.json
File public URL: https://object-store.os-api.cci2.ecmwf.int/meditwin-training/mohanad/meditwin-catalog/stac/sentinel-2-l2a-cog/collection.json
../data/meditwin-catalog/stac/T34SFF_20220209T091131_TCI_10m_cog/T34SFF_20220209T091131_TCI_10m_cog.json
meditwin-catalog/stac/T34SFF_20220209T091131_TCI_10m_cog/T34SFF_20220209T091131_TCI_10m_cog.json
meditwin-catalog/stac/T34SFF_20220209T091131_TCI_10m_cog
T34SFF_20220209T091131_TCI_10m_cog.json
File uploaded successfully to s3://meditwin-training/mohanad/meditwin-catalog/stac/T34SFF_20220209T091131_TCI_10m_cog/T34SFF_20220209T091131_TCI_10m_cog.json
File public URL: https://object-store.os-api.cci2.ecmwf.int/meditwin-training/mohanad/meditwin-catalog/stac/T34SFF_20220209T091131_TCI_10m_cog/T34SFF_20220209T091131_TCI_10m_cog.json
../data/meditwin-catalog/stac/.DS_Store
meditwin-catalog/stac/.DS_Store
meditwin-catalog/stac
.DS_Store
File uploaded successfully to s3://meditwin-training/mohanad/meditwin-catalog/stac/.DS_Store
File public URL: https://object-store.os-api.cci2.ecmwf.int/meditwin-training/mohanad/meditwin-catalog/stac/.DS_Store
../data/meditwin-catalog/stac/catalog.json
meditwin-catalog/stac/catalog.json
meditwin-catalog/stac
catalog.json
File uploaded successfully to s3://meditwin-training/mohanad/meditwin-catalog/stac/catalog.json
File public URL: https://object-store.os-api.cci2.ecmwf.int/meditwin-training/mohanad/meditwin-catalog/stac/catalog.json
Upload completed.

Connect to the catalog using Pystac-client#

MY_STAC_CATALOG  = "https://object-store.os-api.cci2.ecmwf.int/meditwin-training/mohanad/meditwin-catalog/stac/catalog.json"
catalog = pystac_client.Client.open(MY_STAC_CATALOG)
catalog.describe()
/Users/syam/virtualenvs/myvenv/lib/python3.13/site-packages/pystac_client/client.py:186: NoConformsTo: Server does not advertise any conformance classes.
  warnings.warn(NoConformsTo())
/Users/syam/virtualenvs/myvenv/lib/python3.13/site-packages/pystac_client/collection_client.py:149: FallbackToPystac: Falling back to pystac. This might be slow.
  root._warn_about_fallback("ITEM_SEARCH")
* <Client id=meditwin-catalog>
    * <CollectionClient id=sentinel-2-l2a-cog>
      * <Item id=T34SFG_20240621T092031_TCI_10m_cog>
      * <Item id=T34SFF_20220209T091131_TCI_10m_cog>
      * <Item id=T34SEG_20240601T092031_TCI_10m_cog>
      * <Item id=T34SGH_20240608T090601_TCI_10m_cog>
  * <Item id=T34SFG_20240621T092031_TCI_10m_cog>
  * <Item id=T34SFF_20220209T091131_TCI_10m_cog>
  * <Item id=T34SEG_20240601T092031_TCI_10m_cog>
  * <Item id=T34SGH_20240608T090601_TCI_10m_cog>
/Users/syam/virtualenvs/myvenv/lib/python3.13/site-packages/pystac_client/client.py:463: FallbackToPystac: Falling back to pystac. This might be slow.
  self._warn_about_fallback("ITEM_SEARCH")
print (list(catalog.get_all_items()))
[<Item id=T34SFG_20240621T092031_TCI_10m_cog>, <Item id=T34SFF_20220209T091131_TCI_10m_cog>, <Item id=T34SEG_20240601T092031_TCI_10m_cog>, <Item id=T34SGH_20240608T090601_TCI_10m_cog>]

Browser your STAC catalog using STAC Browser#

Go to the link https://radiantearth.github.io/stac-browser Copy-paste the url of your catalog in their and start browsing your catalog. If you have a CORS issue, use the code in the next cell to configure CORS on your S3 bucket

s3 = initiate_s3_client(S3_END_POINT, S3_ACCESS_KEY, S3_SECRET_ACCESS_KEY)

cors_config = {
    'CORSRules': [
        {
            'AllowedOrigins': ['*'],
            'AllowedMethods': ['GET'],
            'AllowedHeaders': ['*'],
            'MaxAgeSeconds': 3000
        }
    ]
}

s3.put_bucket_cors(Bucket=S3_BUCKET_NAME, CORSConfiguration=cors_config)
{'ResponseMetadata': {'RequestId': 'tx00000a693d588cd034b02-006859088d-1db056e8-default',
  'HostId': '',
  'HTTPStatusCode': 200,
  'HTTPHeaders': {'x-amz-request-id': 'tx00000a693d588cd034b02-006859088d-1db056e8-default',
   'content-type': 'application/xml',
   'content-length': '0',
   'date': 'Mon, 23 Jun 2025 07:55:57 GMT'},
  'RetryAttempts': 0}}