S3 File Loader¶
When a
Simulationobject is used in the cloud, where there is no shared disk, KySim simulation files are fetched from S3 on demand instead of being read straight from a local folder. This is handled transparently byS3FileLoader; most users ofkyoslib_pynever need to interact with it directly.
Configuration¶
Simulation switches S3 fetching on only when both of these are true — mirroring the MATLAB
Simulation constructor exactly:
- The KySim settings file has
<KySimInfo><FetchFromS3>1</FetchFromS3></KySimInfo>. - The
KYOS_S3_BUCKETenvironment variable is set (the bucket name itself is never part of the settings file).
If the KySim settings file doesn't ask for S3 at all, Simulation behaves exactly as it always
has: every file is read straight from the folder given in the KySim settings file, with no
network calls at all.
If the settings file does ask for S3 (<FetchFromS3>1</FetchFromS3>) but KYOS_S3_BUCKET
isn't set, that's a misconfiguration, not "off" — what happens next depends on whether this looks
like a deployed container (see KYOS_S3_LOCAL_ROOT below for how that's detected):
- Deployed: raises immediately. A deployed container has no shared disk to fall back to, so
continuing would only fail later, unrecognizably, deep inside whatever file is read first —
this is exactly the failure mode that prompted this behaviour: a missing-bucket
misconfiguration surfacing several files downstream as an unrelated model-specific error (e.g.
an
IndexErrorfrom an empty DataFrame), with no indication the real problem waskyoslib_pynever actually reading anything. - Not deployed: logs a warning and falls back to the settings file's literal path, exactly as
if S3 had never been requested. There's no local-cache fallback to consider here — that cache
can only exist if
KYOS_S3_BUCKETwas already set at some point to build it, so the literal path already covers the only case that can actually happen: a genuine on-prem server run.
| Variable | Purpose | Default |
|---|---|---|
KYOS_S3_BUCKET |
Name of the S3 bucket to fetch simulation files from. | (none) |
KYOS_S3_LOCAL_ROOT |
Parent folder for the local cache. | see below |
There is no region setting: boto3.client('s3') resolves the region itself, the same way every
other AWS call on the same pod already does (AWS_REGION/AWS_DEFAULT_REGION, the AWS config
profile, then instance metadata).
When KYOS_S3_LOCAL_ROOT is not set, S3FileLoader picks it automatically based on whether
KUBERNETES_SERVICE_HOST is set (this is S3FileLoader's own default, so a standalone loader
gets the same answer a Simulation-built one does). Kubernetes sets this itself on every pod in
every cluster, so its presence means the job is genuinely running in the cloud rather than on
the old tenant-host setup — the same question the MATLAB Simulation constructor's
isdeployed() check answers, just via a different signal.
- If
KUBERNETES_SERVICE_HOSTis set, the cache is built one level above the current directory, so it never gets mixed in with the job's own input/output files. - Otherwise (running locally, or still on the old tenant-host setup), the current directory is used directly.
An explicit KYOS_S3_LOCAL_ROOT always overrides both.
S3FileLoader can also be used on its own (outside of Simulation), in which case it falls back
to reading a FETCH_FROM_S3 environment variable (true/1 to enable) instead of an XML flag —
see the class docstring below.
list_dir() names the files and subfolders inside a directory, whether they live locally or
only in S3. Not called from anywhere in kyoslib_py itself yet, but models built on top of
Simulation use it — for example, to figure out a commodity's simulated price granularity
(hourly, half-hourly, or quarter-hourly) from which filenames actually show up in its spot
simulation folder, rather than assuming a fixed naming pattern up front.
Required IAM policy¶
Once S3 fetching is enabled, the pod or container running the model needs an IAM role with:
s3:GetObjectonarn:aws:s3:::<bucket>/simulation/*— to download simulation files.s3:ListBucketonarn:aws:s3:::<bucket>, scoped to thesimulation/*prefix. This is required for correct day-to-day behaviour, not just the one-time startup sanity check. Without it, a request for a file that is genuinely missing from S3 comes back403 Forbiddeninstead of404 Not Found— and the loader cannot tell that apart from a real permissions problem, so it raises an error instead of treating it as "no data for this file". That fallback is exactly what lets things likeload_sims_for_all_monthsstop cleanly at the last available month, so aGetObject-only role will break in ways that only show up once a file is actually missing.list_dir()also needs it whenever it falls back to S3.
Do not confuse s3:ListBucket with s3:ListBuckets (plural). s3:ListBucket only lists the
contents of one named bucket — the sanity check above never does more than that, so granting
it is safe. s3:ListBuckets is a different, account-wide permission that lists every bucket
the credentials can see — kyoslib_py never calls it, it should not be granted, and it is not
required for anything in this repo.
kyoslib_py never configures or grants these permissions itself — it only ever uses whichever
credentials boto3's own default credential chain finds (environment variables locally, the pod's
IAM role in the cloud, or an AWS SSO profile for local development — see
Connecting to AWS S3).
Known limitations¶
- The startup check can reject a bucket that is only temporarily empty. The one-time
sanity check above raises immediately if nothing at all exists yet under the configured
prefix — this is right for catching a genuinely wrong bucket/prefix, but it also means a
brand new client's very first job, before anything has been uploaded for them yet, would
fail at
Simulationconstruction rather than later. There is no MATLAB equivalent to this check (MATLAB never verifies the bucket up front — it only finds out per file, when it tries to load one). kyoslib_pydoes not hardcode a region. Unlike MATLAB, which always passes a region to its S3 client (defaulting to"eu-central-1"unless overridden),kyoslib_pyleaves this toboto3.client('s3'), which resolves it the same way MATLAB's AWS SDK for Java does:AWS_REGION, thenAWS_DEFAULT_REGION, then an AWS config profile, then EC2/EKS instance metadata. Every pod that needs a non-default region already sets one of these for its other AWS calls, so nothing extra is needed here.
Documentation¶
S3FileLoader
¶
Transparently download KySim simulation files from S3 to a local cache folder, so that code written for a shared on-prem disk keeps working unchanged when it runs in a container with no shared disk.
The loader does three things:
rebase()rewrites a shared-disk folder path (e.g. from the KySim XML) onto a local cache folder. This only needs to run once, when aSimulationobject is built.ensure_local_file()checks whether one exact file is already on disk and, if not, downloads it from S3. This has to run every time, right before a file is opened.list_dir()names the files and subfolders inside a directory, whether they exist locally or only in S3.
When fetch_from_s3 is False (the default), all three are pass-throughs and nothing
in kyoslib_py behaves any differently than it does today.
fetch_from_s3=True with no bucket configured is a misconfiguration, not "off" - what
happens next depends on whether this looks like a deployed container:
- Deployed (see
_is_deployed()): raises immediately. There is no shared disk to fall back to, so continuing would only fail later, unrecognizably, deep inside whatever file is read first. - Not deployed: logs a warning and turns
fetch_from_s3off, falling back to the literal on-prem path from the settings file - the same behaviour as if S3 had never been requested at all. There is no local-cache fallback to consider here: the local cache can only exist ifKYOS_S3_BUCKETwas already set at some point to build it.
Configuration is read from environment variables so it works the same way whether a
Simulation object is built from an XML file or directly from a dictionary:
FETCH_FROM_S3("true"/"1" to enable, anything else or unset disables it)KYOS_S3_BUCKET(the S3 bucket name, e.g. "kyos-simulations" - "s3://kyos-simulations" also works, since that's the form MATLAB expects)KYOS_S3_LOCAL_ROOT(optional; where to build the local cache folder - seelocal_root_parentbelow for the default when this is unset)
The AWS region is never configured here - boto3.client('s3') resolves it itself
(AWS_REGION/AWS_DEFAULT_REGION, the AWS config profile, then instance metadata), the
same as every other AWS pod already relies on for every other AWS call it makes.
IAM policy required by the pod/container running the model, once fetch_from_s3 is
enabled:
s3:GetObjectonarn:aws:s3:::<bucket>/<path_anchor>/*(every file download)s3:ListBucketonarn:aws:s3:::<bucket>, scoped to the<path_anchor>/*prefix. This is required for correct behaviour, not just the one-time startup check below: without it, a request for a missing file gets back "403 Forbidden" instead of "404 Not Found", whichensure_local_file()cannot tell apart from a real permissions problem - so a genuinely missing file raises instead of being treated as "no data for this file", exactly the fallback this whole feature depends on (e.g.load_sims_for_all_monthsinSimulationstopping cleanly at the last available month).
Attributes:
| Name | Type | Description |
|---|---|---|
fetch_from_s3 |
bool
|
Whether files should be fetched from S3. |
bucket |
str
|
Name of the S3 bucket to fetch simulation files from. |
path_anchor_local |
str
|
The path segment (e.g. "simulation") that marks where the "interesting" part of a local folder path begins. |
path_anchor_s3 |
str
|
The equivalent path segment on the S3 side. Usually the same as
|
local_root_parent |
Path
|
The folder under which the local cache is built. |
local_root |
Path
|
|
Examples:
>>> from kyoslib_py.s3_file_loader import S3FileLoader
>>> loader = S3FileLoader(fetch_from_s3=True, bucket='kyos-simulations')
>>> spot_folder = loader.rebase('/kyos/data/simulation/Spot/KyPlant')
>>> spot_file = loader.ensure_local_file(spot_folder + '/DailySpot_TTF.mat')
__init__(fetch_from_s3=None, bucket=None, path_anchor='simulation', path_anchor_s3=None, path_anchor_local=None, local_root_parent=None, verbose=False)
¶
Constructor of the S3FileLoader class.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fetch_from_s3
|
bool
|
Whether to fetch missing files from S3. Defaults
to the |
None
|
bucket
|
str
|
S3 bucket name. Defaults to the |
None
|
path_anchor
|
str
|
Shared path segment used for both the local cache and the S3 key layout, unless overridden below. Defaults to "simulation". |
'simulation'
|
path_anchor_s3
|
str
|
Path segment used on the S3 side, if different
from |
None
|
path_anchor_local
|
str
|
Path segment used on the local side, if
different from |
None
|
local_root_parent
|
str
|
Parent folder for the local cache. Defaults to
the |
None
|
verbose
|
bool
|
Print a line for every file fetched from S3. |
False
|
Raises:
| Type | Description |
|---|---|
S3FileLoaderError
|
If |
ensure_local_dir(local_dir)
¶
Ensure every file in a directory is available locally.
Downloads the whole matching S3 prefix when the directory does not exist locally, or
exists but contains no files. Ports MATLAB's EnsureLocalDir (used there to fetch a
job's whole ForwardCurves directory in one go). Not called from anywhere in
kyoslib_py yet, because the directory-based forward-curve reader it would serve
has not been ported to Python yet either -- get_fwd_curve here reads one named CSV
file at a time instead. This is not speculative: add the caller when that reader is
ported, not before.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
local_dir
|
str or Path
|
Path to the local directory, already rebased (i.e. a
descendant of |
required |
Raises:
| Type | Description |
|---|---|
S3FileLoaderError
|
If nothing exists under the matching S3 prefix either, or if a download fails for any other reason (wrong credentials, no network, ...). |
ensure_local_file(local_path)
¶
Ensure a file exists locally, downloading it from S3 first if it is missing.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
local_path
|
str or Path
|
Path to the local file, already rebased (i.e. a
descendant of |
required |
Returns:
| Type | Description |
|---|---|
Path
|
|
Raises:
| Type | Description |
|---|---|
S3FileLoaderError
|
If the download fails for any reason other than the object not existing (wrong credentials, no network, wrong bucket, ...). These are configuration problems, not "no data for this file", so they are always raised rather than treated like a missing file. |
list_dir(local_dir)
¶
List the names of the files and subfolders directly inside a directory, falling back to S3 when nothing has been downloaded into that directory yet.
Downstream models (e.g. KyBattery, KyPlant) call this to work out which simulation files actually exist for a commodity, then infer their time granularity (hourly, half-hourly, or quarter-hourly) from which filenames show up - rather than assuming a fixed naming pattern up front.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
local_dir
|
str or Path
|
Path to the local directory, already rebased (i.e. a
descendant of |
required |
Returns:
| Type | Description |
|---|---|
list[str]
|
Names of the entries directly inside |
Raises:
| Type | Description |
|---|---|
S3FileLoaderError
|
If the S3 listing fails (wrong credentials, no network, ...). |
rebase(folder_path)
¶
Rewrite a shared-disk folder path onto the local cache folder.
Call this once per folder, when a Simulation object is built. It has no effect
(returns its input unchanged) when fetch_from_s3 is False.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
folder_path
|
str
|
A folder path that contains |
required |
Returns:
| Type | Description |
|---|---|
str
|
|
Raises:
| Type | Description |
|---|---|
S3FileLoaderError
|
If |
S3FileLoaderError
¶
Bases: RuntimeError
Raised when a KySim simulation file cannot be fetched from S3 for a reason other than the file simply not existing (for example: wrong credentials, wrong bucket, or no network).
Deliberately not a subclass of OSError. A missing file on-prem raises OSError, and some
calling code catches OSError broadly to mean "no data for this commodity/month, move on".
An expired SSO token or a wrong bucket is a different problem - the job is misconfigured,
not missing data - so it must not be catchable by the same broad except OSError.