Skip to content

Commit

Permalink
Implement the AbstractDict interface for AWSConfig
Browse files Browse the repository at this point in the history
This temporarily makes `AWSConfig` a subtype of `AbstractDict` and
defines the corresponding methods to satisfy the interface. Each method
emits a deprecation warning. This should hopefully be completely
non-breaking for users and provide sufficient information for them to
upgrade.
  • Loading branch information
ararslan committed Feb 2, 2019
1 parent 9771cd0 commit 91943ac
Show file tree
Hide file tree
Showing 2 changed files with 212 additions and 137 deletions.
209 changes: 209 additions & 0 deletions src/AWSConfig.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
"""
AWSConfig
Most `AWSCore` functions take an `AWSConfig` object as the first argument.
This type holds [`AWSCredentials`](@ref), region, and output configuration.
# Constructors
AWSConfig(; profile, creds, region, output)
Construct an `AWSConfig` object with the given profile, credentials, region, and output
format. All keyword arguments have default values and are thus optional.
* `profile`: Profile name passed to [`AWSCredentials`](@ref), or `nothing` (default)
* `creds`: `AWSCredentials` object, constructed using `profile` if not provided
* `region`: Region, read from `AWS_DEFAULT_REGION` if present, otherwise `"us-east-1"`
* `output`: Output format, defaulting to JSON (`"json"`)
# Examples
```julia-repl
julia> AWSConfig(profile="example", region="ap-southeast-2")
AWSConfig(creds=AWSCredentials("AKIDEXAMPLE", "wJa..."), region="ap-southeast-2", output="json")
```
"""
mutable struct AWSConfig <: AbstractDict{Symbol,Any} # XXX: Remove subtype after deprecation
creds::AWSCredentials
region::String
output::String
# XXX: The `_extras` field will be removed after the deprecation period
_extras::Dict{Symbol,Any}
end

function AWSConfig(; profile=nothing,
creds=AWSCredentials(profile=profile),
region=get(ENV, "AWS_DEFAULT_REGION", "us-east-1"),
output="json",
kwargs...)
AWSConfig(creds, region, output, kwargs)
end

function Base.show(io::IO, conf::AWSConfig)
print(io, "AWSConfig(creds=AWSCredentials(")
show(io, conf.creds.access_key_id)
print(io, ", \"", conf.creds.secret_key[1:3], "...\"), region=")
show(io, conf.region)
print(io, ", output=")
show(io, conf.output)
print(io, ')')
if !isempty(conf._extras)
println(io, "\n Additional contents (SHOULD BE REMOVED):")
join(io, [string(" :", k, " => ", v) for (k, v) in conf._extras], '\n')
end
end

# Overrides needed because of the AbstractDict subtyping
Base.summary(io::IO, conf::AWSConfig) = "AWSConfig"
Base.show(io::IO, ::MIME{Symbol("text/plain")}, conf::AWSConfig) = show(io, conf)

function Base.Dict(conf::AWSConfig)
d = copy(conf._extras)
for f in [:creds, :region, :output]
d[f] = getfield(conf, f)
end
d
end

# TODO: Implement copy for AWSCredentials
Base.copy(conf::AWSConfig) = AWSConfig(conf.creds, conf.region, conf.output, copy(conf._extras))

# Relics of using `SymbolDict`. We'll implement the entire `AbstractDict` interface
# with informative deprecation messages depending on how the functions are used:
# if users are storing and accessing the information that corresponds to the fields
# of the type, pretend like we're just using `@deprecate`. If they try it with other
# information, tell them it won't be possible soon.

_isfield(x::Symbol) = (x === :creds || x === :region || x === :output)

function _depmsg(store::Bool)
if store
verb = "storing"
preposition = "in"
else
verb = "retrieving"
preposition = "from"
end
string(verb, " information other than credentials, region, and output format ",
preposition, " an `AWSConfig` object is deprecated; use another data ",
"structure to store this information.")
end

function _depsig(old::String, new::String="")
s = "`" * old * "` is deprecated"
if isempty(new)
s *= "; in the future, no information other than credentials, region, and output " *
"format will be stored in an `AWSConfig` object."
else
s *= ", use `" * new * "` instead."
end
s
end

using Base: @deprecate, depwarn
import Base: merge, merge!, keytype, valtype

@deprecate AWSConfig(pairs::Pair...) AWSConfig(; pairs...)
@deprecate aws_config AWSConfig
@deprecate merge(d::AbstractDict, conf::AWSConfig) merge(d, Dict(conf))
@deprecate merge!(d::AbstractDict, conf::AWSConfig) merge!(d, Dict(conf))
@deprecate keytype(conf::AWSConfig) Symbol
@deprecate valtype(conf::AWSConfig) Any

function Base.setindex!(conf::AWSConfig, val, var::Symbol)
if _isfield(var)
depwarn(_depsig("setindex!(conf::AWSConfig, val, var::Symbol",
"setfield!(conf, var, val)"), :setindex!)
setfield!(conf, var, val)
else
depwarn(_depmsg(true), :setindex!)
conf._extras[var] = val
end
end

function Base.getindex(conf::AWSConfig, x::Symbol)
if _isfield(x)
depwarn(_depsig("getindex(conf::AWSConfig, x::Symbol)",
"getfield(conf, x)"), :getindex)
getfield(conf, x)
else
depwarn(_depmsg(false), :getindex)
conf._extras[x]
end
end

function Base.get(conf::AWSConfig, field::Symbol, alternative)
if _isfield(field)
depwarn(_depsig("get(conf::AWSConfig, field::Symbol, alternative)",
"getfield(conf, field)"), :get)
getfield(conf, field)
else
depwarn(_depmsg(false), :get)
get(conf._extras, field, alternative)
end
end

function Base.haskey(conf::AWSConfig, field::Symbol)
depwarn(_depsig("haskey(conf::AWSConfig, field::Symbol)"), :haskey)
_isfield(field) || haskey(conf._extras, field)
end

function Base.keys(conf::AWSConfig)
depwarn(_depsig("keys(conf::AWSConfig)"), :keys)
keys(Dict(conf))
end

function Base.values(conf::AWSConfig)
depwarn(_depsig("values(conf::AWSConfig)"), :values)
values(Dict(conf))
end

function Base.merge(conf::AWSConfig, d::AbstractDict{Symbol,<:Any})
c = copy(conf)
for (k, v) in d
if _isfield(k)
depwarn("`merge(conf::AWSConf, d::AbstractDict)` is deprecated, set fields " *
"directly instead.", :merge)
setfield!(c, k, v)
else
Base.depwarn(_depmsg(true), :merge)
c._extras[k] = v
end
end
c
end

function Base.merge!(conf::AWSConfig, d::AbstractDict{Symbol,<:Any})
for (k, v) in d
if _isfield(k)
depwarn("`merge!(conf::AWSConf, d::AbstractDict)` is deprecated, set fields " *
"directly instead.", :merge!)
setfield!(conf, k, v)
else
depwarn(_depmsg(true), :merge!)
conf._extras[k] = v
end
end
conf
end

function Base.iterate(conf::AWSConfig, state...)
depwarn("in the future, `AWSConfig` objects will not be iterable.", :iterate)
iterate(Dict(conf), state...)
end

function Base.push!(conf::AWSConfig, (k, v)::Pair{Symbol,<:Any})
if _isfield(conf, k)
depwarn(_depsig("push!(conf::AWSConfig, p::Pair)",
"setfield!(conf, first(p), last(p))"), :push!)
setfield!(conf, k, v)
else
Base.depwarn(_depmsg(true), :push!)
push!(conf._extras, k => v)
end
end

function Base.in((k, v)::Pair, conf::AWSConfig)
depwarn("`in(p::Pair, conf::AWSConfig)` is deprecated.", :in)
(_isfield(k) && getfield(conf, k) == v) || in(p, conf._extras)
end
140 changes: 3 additions & 137 deletions src/AWSCore.jl
Original file line number Diff line number Diff line change
Expand Up @@ -80,141 +80,7 @@ mutable struct AWSCredentials
end
end

"""
AWSConfig
Most `AWSCore` functions take an `AWSConfig` object as the first argument.
This type holds [`AWSCredentials`](@ref), region, and output configuration.
# Constructors
AWSConfig(; profile, creds, region, output)
Construct an `AWSConfig` object with the given profile, credentials, region, and output
format. All keyword arguments have default values and are thus optional.
* `profile`: Profile name passed to [`AWSCredentials`](@ref), or `nothing` (default)
* `creds`: `AWSCredentials` object, constructed using `profile` if not provided
* `region`: Region, read from `AWS_DEFAULT_REGION` if present, otherwise `"us-east-1"`
* `output`: Output format, defaulting to JSON (`"json"`)
# Examples
```julia-repl
julia> AWSConfig(profile="example", region="ap-southeast-2")
AWSConfig((AKIDEXAMPLE, wJa...)
, "ap-southeast-2", "json", NamedTuple())
julia> AWSConfig(creds=AWSCredentials("AKIDEXAMPLE", "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY"))
AWSConfig((AKIDEXAMPLE, wJa...)
, "us-east-1", "json", NamedTuple())
```
"""
mutable struct AWSConfig
creds::AWSCredentials
region::String
output::String
# XXX: The `_extras` field will be removed after the deprecation period
_extras::Dict{Symbol,Any}
end

function AWSConfig(; profile=nothing,
creds=AWSCredentials(profile=profile),
region=get(ENV, "AWS_DEFAULT_REGION", ""),
output="json",
kwargs...)
AWSConfig(creds, region, output, kwargs)
end

# Relics of using SymbolDict

_isfield(x::Symbol) = (x === :creds || x === :region || x === :output)

Base.@deprecate AWSConfig(pairs::Pair...) AWSConfig(; pairs...)
Base.@deprecate aws_config AWSConfig

function Base.setindex!(conf::AWSConfig, val, var::Symbol)
if _isfield(var)
Base.depwarn("`setindex!(conf::AWSConfig, val, var::Symbol)` is deprecated, " *
"use `setfield!(conf, var, val)` instead.", :setindex!)
setfield!(conf, var, val)
else
Base.depwarn("storing information other than credentials, region, and output " *
"format in an `AWSConfig` object is deprecated; use another data " *
"structure to store this information.", :setindex!)
conf._extras[var] = val
end
end

function Base.getindex(conf::AWSConfig, x::Symbol)
if _isfield(x)
Base.depwarn("`getindex(conf::AWSConfig, x::Symbol)` is deprecated, use " *
"`getfield(conf, x)` instead.", :getindex)
getfield(conf, x)
else
Base.depwarn("retrieving information other than credentials, region, and output " *
"format from an `AWSConfig` object is deprecated; use another data " *
"structure to store this information.", :getindex)
conf._extras[x]
end
end

function Base.get(conf::AWSConfig, field::Symbol, alternative)
if _isfield(field)
Base.depwarn("`get(conf::AWSConfig, field::Symbol, alternative)` is deprecated, " *
"use `getfield(conf, field)` instead.", :get)
getfield(conf, field)
else
Base.depwarn("retrieving information other than credentials, region, and output " *
"format from an `AWSConfig` object is deprecated; use another data " *
"structure to store this information.", :get)
get(conf._extras, field, alternative)
end
end

function Base.haskey(conf::AWSConfig, field::Symbol)
Base.depwarn("`haskey(conf::AWSConfig, field::Symbol)` is deprecated; in the future " *
"no information other than credentials, region and output format will " *
"be stored in an `AWSConfig` object.", :haskey)
_isfield(field) ? true : haskey(conf._extras, field)
end

function Base.merge(conf::AWSConfig, d::AbstractDict{Symbol,<:Any})
for (k, v) in d
if _isfield(k)
Base.depwarn("`merge(conf::AWSConf, d::AbstractDict)` is deprecated, set fields " *
"directly instead", :merge)
setfield!(conf, k, v)
else
Base.depwarn("storing information other than credentials, region, and output " *
"format in an `AWSConfig` object is deprecated; use another data " *
"structure to store this information.", :merge)
conf._extras[k] = v
end
end
conf
end

function Base.merge(d::AbstractDict{K,V}, conf::AWSConfig) where {K,V}
Base.depwarn("`merge(d::AbstractDict, conf::AWSConfig)` is deprecated; in the future " *
"no information other than credentials, region and output format will " *
"be stored in an `AWSConfig` object and it will not behave like a " *
"dictionary.", :merge)
m = merge(d, conf._extras)
for f in [:creds, :region, :output]
m[convert(K, f)] = getfield(conf, f)
end
m
end

function Base.iterate(conf::AWSConfig, state...)
Base.depwarn("in the future, `AWSConfig` objects will not be iterable", :iterate)
x = [:creds => conf.creds,
:region => conf.region,
:output => conf.output,
conf._extras...]
iterate(x, state...)
end
include("AWSConfig.jl")

"""
The `AWSRequest` dictionary describes a single API request:
Expand Down Expand Up @@ -348,7 +214,7 @@ function service_query(aws::AWSConfig; args...)
request = Dict{Symbol,Any}(args)

request[:verb] = "POST"
request[:resource] = get(aws, :resource, "/")
request[:resource] = get(aws._extras, :resource, "/")
request[:url] = service_url(aws, request)
request[:headers] = Dict("Content-Type" =>
"application/x-www-form-urlencoded; charset=utf-8")
Expand All @@ -366,7 +232,7 @@ function service_query(aws::AWSConfig; args...)

request[:content] = HTTP.escapeuri(flatten_query(request[:service],
request[:query]))
do_request(merge(request, aws))
do_request(merge(request, Dict(aws)))
end


Expand Down

0 comments on commit 91943ac

Please sign in to comment.