Skip to content

Commit 0cdaa80

Browse files
authored
Try #175:
2 parents c4be852 + 09849c3 commit 0cdaa80

File tree

4 files changed

+66
-27
lines changed

4 files changed

+66
-27
lines changed

Project.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ version = "0.8.8"
55
[deps]
66
AWS = "fbe9abb3-538b-5e4e-ba9e-bc94f4f92ebc"
77
Base64 = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"
8+
Compat = "34da2185-b29b-5c13-b0c7-acf172513d20"
89
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
910
EzXML = "8f5d6c58-4d21-5cfd-889c-e3ad7ee6a615"
1011
FilePathsBase = "48062228-2e41-5def-b9a4-89aafe57970f"
@@ -19,6 +20,7 @@ XMLDict = "228000da-037f-5747-90a9-8195ccbf91a5"
1920

2021
[compat]
2122
AWS = "1.25"
23+
Compat = "3.29.0"
2224
EzXML = "0.9, 1"
2325
FilePathsBase = "0.9"
2426
HTTP = "0.8, 0.9"

src/AWSS3.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,13 @@ using Dates
5050
using Base64
5151
using UUIDs
5252
using URIs
53+
using Compat: @something
5354

5455
@service S3
5556

5657
const SSDict = Dict{String,String}
5758
const AbstractS3Version = Union{AbstractString,Nothing}
59+
const AbstractS3PathConfig = Union{AbstractAWSConfig,Nothing}
5860

5961
__init__() = FilePathsBase.register(S3Path)
6062

src/s3path.jl

Lines changed: 28 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
struct S3Path{A<:AbstractAWSConfig} <: AbstractPath
1+
struct S3Path{A<:AbstractS3PathConfig} <: AbstractPath
22
segments::Tuple{Vararg{String}}
33
root::String
44
drive::String
@@ -21,7 +21,7 @@ function S3Path(
2121
drive::AbstractString,
2222
isdirectory::Bool,
2323
version::AbstractS3Version,
24-
config::AbstractAWSConfig,
24+
config::AbstractS3PathConfig,
2525
)
2626
return S3Path{typeof(config)}(segments, root, drive, isdirectory, version, config)
2727
end
@@ -40,7 +40,7 @@ end
4040

4141
"""
4242
S3Path()
43-
S3Path(str; version=nothing, config::AbstractAWSConfig=global_aws_config())
43+
S3Path(str; config::$(AbstractS3PathConfig)=nothing, version=nothing)
4444
4545
Construct a new AWS S3 path type which should be of the form
4646
"s3://<bucket>/prefix/to/my/object".
@@ -60,23 +60,23 @@ NOTES:
6060
- If `version` argument is `nothing`, will return latest version of object. Version
6161
can be provided via either kwarg `version` or as suffix "?versionId=<object_version>"
6262
of `str`, e.g., "s3://<bucket>/prefix/to/my/object?versionId=<object_version>".
63+
- If `config` is left at its default value of `nothing`, then the
64+
latest `global_aws_config()` will be used in any operations involving the
65+
path. To "freeze" the config at construction time, explicitly pass an
66+
`AbstractAWSConfig` to the `config` keyword argument.
67+
- If `version` argument is `nothing`, will return latest version of object.
6368
"""
64-
function S3Path()
65-
config = global_aws_config()
66-
account_id = aws_account_number(config)
67-
region = config.region
69+
S3Path() = S3Path((), "/", "", true, nothing, nothing)
6870

69-
return S3Path((), "/", "s3://$account_id-$region", true, nothing, config)
70-
end
7171
# below definition needed by FilePathsBase
72-
S3Path{A}() where {A<:AbstractAWSConfig} = S3Path()
72+
S3Path{A}() where {A<:AbstractS3PathConfig} = S3Path()
7373

7474
function S3Path(
7575
bucket::AbstractString,
7676
key::AbstractString;
7777
isdirectory::Bool=false,
7878
version::AbstractS3Version=nothing,
79-
config::AbstractAWSConfig=global_aws_config(),
79+
config::AbstractS3PathConfig=nothing,
8080
)
8181
return S3Path(
8282
Tuple(filter!(!isempty, split(key, "/"))),
@@ -93,7 +93,7 @@ function S3Path(
9393
key::AbstractPath;
9494
isdirectory::Bool=false,
9595
version::AbstractS3Version=nothing,
96-
config::AbstractAWSConfig=global_aws_config(),
96+
config::AbstractS3PathConfig=nothing,
9797
)
9898
return S3Path(
9999
key.segments, "/", normalize_bucket_name(bucket), isdirectory, version, config
@@ -103,8 +103,8 @@ end
103103
# To avoid a breaking change.
104104
function S3Path(
105105
str::AbstractString;
106-
config::AbstractAWSConfig=global_aws_config(),
107106
version::AbstractS3Version=nothing,
107+
config::AbstractS3PathConfig=nothing,
108108
)
109109
result = tryparse(S3Path, str; config=config)
110110
result !== nothing || throw(ArgumentError("Invalid s3 path string: $str"))
@@ -117,16 +117,12 @@ function S3Path(
117117
return result
118118
end
119119

120-
# if config=nothing, will not try to talk to AWS until after string is confirmed to be an s3 path
121120
function Base.tryparse(
122-
::Type{<:S3Path}, str::AbstractString; config::Union{Nothing,AbstractAWSConfig}=nothing
121+
::Type{<:S3Path}, str::AbstractString; config::AbstractS3PathConfig=nothing
123122
)
124123
uri = URI(str)
125124
uri.scheme == "s3" || return nothing
126125

127-
# we do this here so that the `@p_str` macro only tries to call AWS if it actually has an S3 path
128-
config === nothing && (config = global_aws_config())
129-
130126
drive = "s3://$(uri.host)"
131127
root = isempty(uri.path) ? "" : "/"
132128
isdirectory = isempty(uri.path) || endswith(uri.path, '/')
@@ -207,9 +203,13 @@ function FilePathsBase.parents(fp::S3Path)
207203
end
208204
end
209205

206+
# Use `fp.config` unless it is nothing; in that case, get the latest `global_aws_config`
207+
get_config(fp::S3Path) = @something(fp.config, global_aws_config())
208+
210209
function FilePathsBase.exists(fp::S3Path)
211-
return s3_exists(fp.config, fp.bucket, fp.key; version=fp.version)
210+
return s3_exists(get_config(fp), fp.bucket, fp.key; version=fp.version)
212211
end
212+
213213
Base.isfile(fp::S3Path) = !fp.isdirectory && exists(fp)
214214
function Base.isdir(fp::S3Path)
215215
if isempty(fp.segments)
@@ -220,7 +220,7 @@ function Base.isdir(fp::S3Path)
220220
return false
221221
end
222222

223-
objects = s3_list_objects(fp.config, fp.bucket, key; max_items=1)
223+
objects = s3_list_objects(get_config(fp), fp.bucket, key; max_items=1)
224224

225225
# `objects` is a `Channel`, so we call iterate to see if there are any objects that
226226
# match our directory key.
@@ -231,7 +231,7 @@ end
231231

232232
function FilePathsBase.walkpath(fp::S3Path; kwargs...)
233233
# Select objects with that prefix
234-
objects = s3_list_objects(fp.config, fp.bucket, fp.key; delimiter="")
234+
objects = s3_list_objects(get_config(fp), fp.bucket, fp.key; delimiter="")
235235

236236
# Construct a new Channel using a recursive internal `_walkpath!` function
237237
return Channel(; ctype=typeof(fp)) do chnl
@@ -299,7 +299,8 @@ function Base.stat(fp::S3Path)
299299
last_modified = DateTime(0)
300300

301301
if exists(fp)
302-
resp = s3_get_meta(fp.config, fp.bucket, fp.key; version=fp.version)
302+
resp = s3_get_meta(get_config(fp), fp.bucket, fp.key; version=fp.version)
303+
303304
# Example: "Thu, 03 Jan 2019 21:09:17 GMT"
304305
last_modified = DateTime(
305306
resp["Last-Modified"][1:(end - 4)], dateformat"e, d u Y H:M:S"
@@ -354,7 +355,7 @@ function Base.rm(fp::S3Path; recursive=false, kwargs...)
354355
end
355356

356357
@debug "delete: $fp"
357-
return s3_delete(fp.config, fp.bucket, fp.key; version=fp.version)
358+
return s3_delete(get_config(fp), fp.bucket, fp.key; version=fp.version)
358359
end
359360

360361
# We need to special case sync with S3Paths because of how directories
@@ -478,7 +479,7 @@ function Base.readdir(fp::S3Path; join=false, sort=true)
478479
if !isempty(token)
479480
params["continuation-token"] = token
480481
end
481-
S3.list_objects_v2(fp.bucket, params; aws_config=fp.config)
482+
S3.list_objects_v2(fp.bucket, params; aws_config=get_config(fp))
482483
catch e
483484
@delay_retry if ecode(e) in ["NoSuchBucket"]
484485
end
@@ -502,7 +503,7 @@ end
502503
function Base.read(fp::S3Path; byte_range=nothing)
503504
return Vector{UInt8}(
504505
s3_get(
505-
fp.config,
506+
get_config(fp),
506507
fp.bucket,
507508
fp.key;
508509
raw=true,
@@ -528,11 +529,11 @@ function Base.write(
528529
fp.version === nothing ||
529530
throw(ArgumentError("Can't write to a specific object version ($(fp.version))"))
530531
if !multipart || length(content) < MAX_HTTP_BYTES
531-
return s3_put(fp.config, fp.bucket, fp.key, content)
532+
return s3_put(get_config(fp), fp.bucket, fp.key, content)
532533
else
533534
io = IOBuffer(content)
534535
return s3_multipart_upload(
535-
fp.config, fp.bucket, fp.key, io, part_size_mb; other_kwargs...
536+
get_config(fp), fp.bucket, fp.key, io, part_size_mb; other_kwargs...
536537
)
537538
end
538539
end

test/s3path.jl

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -547,6 +547,40 @@ function s3path_tests(config)
547547
end
548548
end
549549

550+
# <https://github.com/JuliaCloud/AWSS3.jl/issues/168>
551+
@testset "Default `S3Path` does not hold config" begin
552+
path = S3Path("s3://$(bucket_name)/test_str.txt")
553+
@test path.config === nothing
554+
@test AWSS3.get_config(path) !== nothing
555+
end
556+
557+
# Minio does not care about regions, so this test doesn't work there
558+
if is_aws(config)
559+
@testset "Global config is not frozen at construction time" begin
560+
prev_config = global_aws_config()
561+
562+
# Setup: create a file holding a string `abc`
563+
path = S3Path("s3://$(bucket_name)/test_str.txt")
564+
write(path, "abc")
565+
@test read(path, String) == "abc" # Have access to read file
566+
567+
alt_region = prev_config.region == "us-east-2" ? "us-east-1" : "us-east-2"
568+
try
569+
global_aws_config(; region=alt_region) # this is the wrong region!
570+
@test_throws AWS.AWSException read(path, String)
571+
572+
# restore the right region
573+
global_aws_config(prev_config)
574+
# Now it works, without recreating `path`
575+
@test read(path, String) == "abc"
576+
rm(path)
577+
finally
578+
# In case a test threw, make sure we really do restore the right global config
579+
global_aws_config(prev_config)
580+
end
581+
end
582+
end
583+
550584
# Broken on minio
551585
if is_aws(config)
552586
AWSS3.s3_nuke_bucket(config, bucket_name)

0 commit comments

Comments
 (0)