Merge branch 'issue-135' into 'master'

Options

Closes #135

See merge request honeyryderchuck/httpx!148
This commit is contained in:
HoneyryderChuck 2021-07-14 17:31:11 +00:00
commit 2ea8b2ec2d
30 changed files with 250 additions and 268 deletions

View File

@ -30,6 +30,7 @@ module HTTPX
:request_body_class => Class.new(Request::Body),
:response_body_class => Class.new(Response::Body),
:connection_class => Class.new(Connection),
:options_class => Class.new(self),
:transport => nil,
:transport_options => nil,
:addresses => nil,
@ -41,39 +42,48 @@ module HTTPX
class << self
def new(options = {})
# let enhanced options go through
return options if self == Options && options.class > self
return options if self == Options && options.class < self
return options if options.is_a?(self)
super
end
def def_option(name, layout = nil, &interpreter)
attr_reader name
def method_added(meth)
super
return unless meth =~ /^option_(.+)$/
optname = Regexp.last_match(1).to_sym
attr_reader(optname)
end
def def_option(optname, *args, &block)
if args.size.zero? && !block_given?
class_eval(<<-OUT, __FILE__, __LINE__ + 1)
def option_#{optname}(v); v; end
OUT
return
end
deprecated_def_option(optname, *args, &block)
end
def deprecated_def_option(optname, layout = nil, &interpreter)
warn "DEPRECATION WARNING: using `def_option(#{optname})` for setting options is deprecated. " \
"Define module OptionsMethods and `def option_#{optname}(val)` instead."
if layout
class_eval(<<-OUT, __FILE__, __LINE__ + 1)
def #{name}=(value)
return if value.nil?
value = begin
def option_#{optname}(value)
#{layout}
end
@#{name} = value
end
OUT
elsif interpreter
define_method(:"#{name}=") do |value|
return if value.nil?
instance_variable_set(:"@#{name}", instance_exec(value, &interpreter).freeze)
elsif block_given?
define_method(:"option_#{optname}") do |value|
instance_exec(value, &interpreter)
end
else
attr_writer name
end
protected :"#{name}="
end
end
@ -83,7 +93,8 @@ module HTTPX
next if v.nil?
begin
__send__(:"#{k}=", v)
value = __send__(:"option_#{k}", v)
instance_variable_set(:"@#{k}", value)
rescue NoMethodError
raise Error, "unknown option: #{k}"
end
@ -91,19 +102,15 @@ module HTTPX
freeze
end
def_option(:origin, <<-OUT)
def option_origin(value)
URI(value)
OUT
end
def_option(:headers, <<-OUT)
if self.headers
self.headers.merge(value)
else
def option_headers(value)
Headers.new(value)
end
OUT
def_option(:timeout, <<-OUT)
def option_timeout(value)
timeouts = Hash[value]
if timeouts.key?(:loop_timeout)
@ -112,42 +119,43 @@ module HTTPX
end
timeouts
OUT
end
def_option(:max_concurrent_requests, <<-OUT)
def option_max_concurrent_requests(value)
raise TypeError, ":max_concurrent_requests must be positive" unless value.positive?
value
OUT
end
def_option(:max_requests, <<-OUT)
def option_max_requests(value)
raise TypeError, ":max_requests must be positive" unless value.positive?
value
OUT
end
def_option(:window_size, <<-OUT)
def option_window_size(value)
Integer(value)
OUT
end
def_option(:body_threshold_size, <<-OUT)
def option_body_threshold_size(value)
Integer(value)
OUT
end
def_option(:transport, <<-OUT)
def option_transport(value)
transport = value.to_s
raise TypeError, "\#{transport} is an unsupported transport type" unless IO.registry.key?(transport)
transport
OUT
end
def_option(:addresses, <<-OUT)
def option_addresses(value)
Array(value)
OUT
end
%i[
params form json body ssl http2_settings
request_class response_class headers_class request_body_class response_body_class connection_class
request_class response_class headers_class request_body_class
response_body_class connection_class options_class
io fallback_protocol debug debug_level transport_options resolver_class resolver_options
persistent
].each do |method_name|

View File

@ -141,22 +141,23 @@ module HTTPX
end
class << self
def extra_options(options)
Class.new(options.class) do
def_option(:sigv4_signer, <<-OUT)
value.is_a?(#{Signer}) ? value : #{Signer}.new(value)
OUT
end.new(options)
end
def load_dependencies(klass)
def load_dependencies(*)
require "digest/sha2"
require "openssl"
end
def configure(klass)
klass.plugin(:expect)
klass.plugin(:compression)
end
end
module OptionsMethods
def option_sigv4_signer(value)
value.is_a?(Signer) ? value : Signer.new(value)
end
end
module InstanceMethods
def aws_sigv4_authentication(**options)
with(sigv4_signer: Signer.new(**options))

View File

@ -23,21 +23,22 @@ module HTTPX
encodings = Module.new do
extend Registry
end
options.merge(encodings: encodings)
end
end
Class.new(options.class) do
def_option(:compression_threshold_size, <<-OUT)
module OptionsMethods
def option_compression_threshold_size(value)
bytes = Integer(value)
raise TypeError, ":expect_threshold_size must be positive" unless bytes.positive?
bytes
OUT
end
def_option(:encodings, <<-OUT)
def option_encodings(value)
raise TypeError, ":encodings must be a registry" unless value.respond_to?(:registry)
value
OUT
end.new(options).merge(encodings: encodings)
end
end

View File

@ -18,12 +18,10 @@ module HTTPX
require "httpx/plugins/cookies/set_cookie_parser"
end
def self.extra_options(options)
Class.new(options.class) do
def_option(:cookies, <<-OUT)
value.is_a?(#{Jar}) ? value : #{Jar}.new(value)
OUT
end.new(options)
module OptionsMethods
def option_cookies(value)
value.is_a?(Jar) ? value : Jar.new(value)
end
end
module InstanceMethods

View File

@ -14,20 +14,24 @@ module HTTPX
DigestError = Class.new(Error)
def self.extra_options(options)
Class.new(options.class) do
def_option(:digest, <<-OUT)
raise TypeError, ":digest must be a Digest" unless value.is_a?(#{Digest})
value
OUT
end.new(options).merge(max_concurrent_requests: 1)
class << self
def extra_options(options)
options.merge(max_concurrent_requests: 1)
end
def self.load_dependencies(*)
def load_dependencies(*)
require "securerandom"
require "digest"
end
end
module OptionsMethods
def option_digest(value)
raise TypeError, ":digest must be a Digest" unless value.is_a?(Digest)
value
end
end
module InstanceMethods
def digest_authentication(user, password)

View File

@ -10,26 +10,30 @@ module HTTPX
module Expect
EXPECT_TIMEOUT = 2
def self.no_expect_store
class << self
def no_expect_store
@no_expect_store ||= []
end
def self.extra_options(options)
Class.new(options.class) do
def_option(:expect_timeout, <<-OUT)
def extra_options(options)
options.merge(expect_timeout: EXPECT_TIMEOUT)
end
end
module OptionsMethods
def option_expect_timeout(value)
seconds = Integer(value)
raise TypeError, ":expect_timeout must be positive" unless seconds.positive?
seconds
OUT
end
def_option(:expect_threshold_size, <<-OUT)
def option_expect_threshold_size(value)
bytes = Integer(value)
raise TypeError, ":expect_threshold_size must be positive" unless bytes.positive?
bytes
OUT
end.new(options).merge(expect_timeout: EXPECT_TIMEOUT)
end
end
module RequestMethods

View File

@ -17,17 +17,17 @@ module HTTPX
MAX_REDIRECTS = 3
REDIRECT_STATUS = (300..399).freeze
def self.extra_options(options)
Class.new(options.class) do
def_option(:max_redirects, <<-OUT)
module OptionsMethods
def option_max_redirects(value)
num = Integer(value)
raise TypeError, ":max_redirects must be positive" if num.negative?
num
OUT
end
def_option(:follow_insecure_redirects)
end.new(options)
def option_follow_insecure_redirects(value)
value
end
end
module InstanceMethods

View File

@ -61,36 +61,7 @@ module HTTPX
end
def extra_options(options)
Class.new(options.class) do
def_option(:grpc_service, <<-OUT)
String(value)
OUT
def_option(:grpc_compression, <<-OUT)
case value
when true, false
value
else
value.to_s
end
OUT
def_option(:grpc_rpcs, <<-OUT)
Hash[value]
OUT
def_option(:grpc_deadline, <<-OUT)
raise TypeError, ":grpc_deadline must be positive" unless value.positive?
value
OUT
def_option(:call_credentials, <<-OUT)
raise TypeError, ":call_credentials must respond to #call" unless value.respond_to?(:call)
value
OUT
end.new(options).merge(
options.merge(
fallback_protocol: "h2",
http2_settings: { wait_for_handshake: false },
grpc_rpcs: {}.freeze,
@ -100,6 +71,37 @@ module HTTPX
end
end
module OptionsMethods
def option_grpc_service(value)
String(value)
end
def option_grpc_compression(value)
case value
when true, false
value
else
value.to_s
end
end
def option_grpc_rpcs(value)
Hash[value]
end
def option_grpc_deadline(value)
raise TypeError, ":grpc_deadline must be positive" unless value.positive?
value
end
def option_call_credentials(value)
raise TypeError, ":call_credentials must respond to #call" unless value.respond_to?(:call)
value
end
end
module ResponseMethods
attr_reader :trailing_metadata

View File

@ -15,13 +15,15 @@ module HTTPX
end
def extra_options(options)
Class.new(options.class) do
def_option(:ntlm, <<-OUT)
raise TypeError, ":ntlm must be a #{NTLMParams}" unless value.is_a?(#{NTLMParams})
options.merge(max_concurrent_requests: 1)
end
end
module OptionsMethods
def option_ntlm(value)
raise TypeError, ":ntlm must be a #{NTLMParams}" unless value.is_a?(NTLMParams)
value
OUT
end.new(options).merge(max_concurrent_requests: 1)
end
end

View File

@ -64,13 +64,11 @@ module HTTPX
klass.plugin(:"proxy/socks4")
klass.plugin(:"proxy/socks5")
end
end
def extra_options(options)
Class.new(options.class) do
def_option(:proxy, <<-OUT)
value.is_a?(#{Parameters}) ? value : Hash[value]
OUT
end.new(options)
module OptionsMethods
def option_proxy(value)
value.is_a?(Parameters) ? value : Hash[value]
end
end

View File

@ -6,16 +6,16 @@ module HTTPX
module Plugins
module Proxy
module SSH
def self.load_dependencies(*)
class << self
def load_dependencies(*)
require "net/ssh/gateway"
end
end
def self.extra_options(options)
Class.new(options.class) do
def_option(:proxy, <<-OUT)
module OptionsMethods
def option_proxy(value)
Hash[value]
OUT
end.new(options)
end
end
module InstanceMethods

View File

@ -24,8 +24,11 @@ module HTTPX
Errno::ETIMEDOUT].freeze
def self.extra_options(options)
Class.new(options.class) do
def_option(:retry_after, <<-OUT)
options.merge(max_retries: MAX_RETRIES)
end
module OptionsMethods
def option_retry_after(value)
# return early if callable
unless value.respond_to?(:call)
value = Integer(value)
@ -33,23 +36,24 @@ module HTTPX
end
value
OUT
end
def_option(:max_retries, <<-OUT)
def option_max_retries(value)
num = Integer(value)
raise TypeError, ":max_retries must be positive" unless num.positive?
num
OUT
end
def_option(:retry_change_requests)
def option_retry_change_requests(v)
v
end
def_option(:retry_on, <<-OUT)
def option_retry_on(value)
raise ":retry_on must be called with the response" unless value.respond_to?(:call)
value
OUT
end.new(options).merge(max_retries: MAX_RETRIES)
end
end
module InstanceMethods

View File

@ -18,14 +18,15 @@ module HTTPX
upgrade_handlers = Module.new do
extend Registry
end
options.merge(upgrade_handlers: upgrade_handlers)
end
end
Class.new(options.class) do
def_option(:upgrade_handlers, <<-OUT)
module OptionsMethods
def option_upgrade_handlers(value)
raise TypeError, ":upgrade_handlers must be a registry" unless value.respond_to?(:registry)
value
OUT
end.new(options).merge(upgrade_handlers: upgrade_handlers)
end
end

View File

@ -247,9 +247,8 @@ module HTTPX
if !@plugins.include?(pl)
@plugins << pl
pl.load_dependencies(self, &block) if pl.respond_to?(:load_dependencies)
@default_options = @default_options.dup
@default_options = pl.extra_options(@default_options, &block) if pl.respond_to?(:extra_options)
@default_options = @default_options.merge(options) if options
include(pl::InstanceMethods) if defined?(pl::InstanceMethods)
extend(pl::ClassMethods) if defined?(pl::ClassMethods)
@ -266,14 +265,26 @@ module HTTPX
opts.response_body_class.__send__(:include, pl::ResponseBodyMethods) if defined?(pl::ResponseBodyMethods)
opts.response_body_class.extend(pl::ResponseBodyClassMethods) if defined?(pl::ResponseBodyClassMethods)
opts.connection_class.__send__(:include, pl::ConnectionMethods) if defined?(pl::ConnectionMethods)
if defined?(pl::OptionsMethods)
opts.options_class.__send__(:include, pl::OptionsMethods)
(pl::OptionsMethods.instance_methods - Object.instance_methods).each do |meth|
opts.options_class.method_added(meth)
end
@default_options = opts.options_class.new(opts)
end
@default_options = pl.extra_options(@default_options) if pl.respond_to?(:extra_options)
@default_options = @default_options.merge(options) if options
pl.configure(self, &block) if pl.respond_to?(:configure)
@default_options.freeze
elsif options
# this can happen when two plugins are loaded, an one of them calls the other under the hood,
# albeit changing some default.
@default_options = @default_options.dup
@default_options = @default_options.merge(options)
@default_options = pl.extra_options(@default_options) if pl.respond_to?(:extra_options)
@default_options = @default_options.merge(options) if options
@default_options.freeze
end

View File

@ -13,66 +13,51 @@ module HTTPX
type timeout_type = :connect_timeout | :settings_timeout | :operation_timeout | :keep_alive_timeout | :total_timeout
type timeout = Hash[timeout_type, Numeric?]
def self.new: (options) -> instance
| () -> instance
def self.new: (?options) -> instance
def self.def_option: (Symbol, ?String) -> void
| (Symbol) { (*nil) -> untyped } -> void
# headers
attr_reader uri: URI?
def uri=: (uri) -> void
# headers
attr_reader headers: Headers?
def headers=: (headers) -> void
# timeout
attr_reader timeout: timeout
def timeout=: (timeout) -> void
# max_concurrent_requests
attr_reader max_concurrent_requests: Integer?
def max_concurrent_requests=: (Integer) -> void
# max_requests
attr_reader max_requests: Integer?
def max_requests=: (Integer) -> void
# window_size
attr_reader window_size: Integer
def window_size=: (int) -> void
# body_threshold_size
attr_reader body_threshold_size: Integer
def body_threshold_size=: (int) -> void
# transport
attr_reader transport: String?
def transport=: (_ToS) -> void
# transport_options
attr_reader transport_options: Hash[untyped, untyped]?
def transport_options=: (Hash[untyped, untyped]) -> void
# addresses
attr_reader addresses: _ToAry[ipaddr]?
def addresses=: (_ToAry[ipaddr]) -> void
# params
attr_reader params: Transcoder::urlencoded_input?
def params=: (Transcoder::urlencoded_input) -> void
# form
attr_reader form: Transcoder::urlencoded_input?
def form=: (Transcoder::urlencoded_input) -> void
# json
attr_reader json: _ToJson?
def json=: (_ToJson) -> void
# body
attr_reader body: bodyIO?
def body=: (bodyIO) -> void
# ssl
@ -81,25 +66,18 @@ module HTTPX
# classes
attr_reader connection_class: singleton(Connection)
def connection_class=: (singleton(Connection)) -> void
attr_reader request_class: singleton(Request)
def request_class=: (singleton(Request)) -> void
attr_reader response_class: singleton(Response)
def response_class=: (singleton(Response)) -> void
attr_reader headers_class: singleton(Headers)
def headers_class=: (singleton(Headers)) -> void
attr_reader request_body_class: singleton(Request::Body)
def request_body_class=: (singleton(Request::Body)) -> void
attr_reader response_body_class: singleton(Response::Body)
def response_body_class=: (singleton(Response::Body)) -> void
attr_reader ssl: Hash[Symbol, untyped]
def ssl=: (Hash[Symbol, untyped]) -> void
# request_class response_class headers_class request_body_class
# response_body_class connection_class
@ -108,27 +86,21 @@ module HTTPX
# io
type io_option = _ToIO | Hash[String, _ToIO]
attr_reader io: io_option?
def io=: (io_option) -> void
# fallback_protocol
attr_reader fallback_protocol: String?
def fallback_protocol=: (String) -> void
# debug
attr_reader debug: _IOLogger?
def debug=: (_IOLogger) -> void
# debug_level
attr_reader debug_level: Integer
def debug_level=: (Integer) -> void
# persistent
attr_reader persistent: bool?
def persistent=: (bool) -> void
# resolver_options
attr_reader resolver_options: Hash[Symbol, untyped]?
def resolver_options=: (Hash[Symbol, untyped]) -> void
def ==: (untyped other) -> bool
def merge: (_ToHash[Symbol | String, untyped] other) -> instance

View File

@ -42,7 +42,6 @@ module HTTPX
interface _SigV4Options
def sigv4_signer: () -> Signer?
def sigv4_signer=: (Signer) -> Signer
end
def self.extra_options: (Options) -> (Options & _SigV4Options)

View File

@ -20,10 +20,8 @@ module HTTPX
interface _CompressionOptions
def compression_threshold_size: () -> Integer?
def compression_threshold_size=: (int) -> _ToInt
def encodings: () -> encodings_registry?
def encodings=: (encodings_registry) -> encodings_registry
end
def self.extra_options: (Options) -> (Options & _CompressionOptions)

View File

@ -5,7 +5,6 @@ module HTTPX
interface _CookieOptions
def cookies: () -> Jar?
def cookies=: (jar) -> Jar
end
def self.extra_options: (Options) -> (Options & _CookieOptions)

View File

@ -5,7 +5,6 @@ module HTTPX
interface _DigestOptions
def digest: () -> Digest?
def digest=: (Digest) -> Digest
end
def self.extra_options: (Options) -> (Options & _DigestOptions)

View File

@ -5,10 +5,8 @@ module HTTPX
interface _ExpectOptions
def expect_timeout: () -> Integer?
def expect_timeout=: (int) -> Integer
def expect_threshold_size: () -> Integer?
def expect_threshold_size=: (int) -> Integer
end
def self.extra_options: (Options) -> (Options & _ExpectOptions)

View File

@ -8,10 +8,8 @@ module HTTPX
interface _FollowRedirectsOptions
def max_redirects: () -> Integer?
def max_redirects=: (int) -> Integer
def follow_insecure_redirects: () -> bool?
def follow_insecure_redirects=: (bool) -> bool
end
def self.extra_options: (Options) -> (Options & _FollowRedirectsOptions)

View File

@ -49,19 +49,14 @@ module HTTPX
interface _GRPCOptions
def grpc_service: () -> String?
def grpc_service=: (string) -> String
def grpc_compression: () -> compression_option?
def grpc_compression=: (compression_option) -> compression_option
def grpc_rpcs: () -> Hash[String, rpc_def]?
def grpc_rpcs=: (Hash[String, rpc_def]) -> Hash[String, rpc_def]
def grpc_deadline: () -> Integer?
def grpc_deadline=: (Integer) -> Integer
def call_credentials: () -> credentials?
def call_credentials=: (credentials) -> credentials
end
def self.extra_options: (Options) -> (Options & _GRPCOptions)

View File

@ -4,7 +4,6 @@ module HTTPX
interface _NTLMOptions
def ntlm: () -> NTLMParams?
def ntlm=: (NTLMParams) -> NTLMParams
end
def self.extra_options: (Options) -> (Options & _NTLMOptions)

View File

@ -3,12 +3,7 @@ module HTTPX
module Persistent
def self.load_dependencies: (singleton(Session)) -> void
interface _PersistentOptions
def persistent: () -> bool?
def persistent=: (bool) -> bool
end
def self.extra_options: (Options) -> (Options & _PersistentOptions)
def self.extra_options: (Options) -> (Options)
end
type sessionPersistent = sessionFollowRedirects

View File

@ -28,7 +28,6 @@ module HTTPX
interface _ProxyOptions
def proxy: () -> proxyParam?
def proxy=: (Parameters | _ToHash[Symbol, untyped]) -> proxyParam
end
def self.extra_options: (Options) -> (Options & _ProxyOptions)

View File

@ -1,8 +1,6 @@
module HTTPX
module Plugins
module PushPromise
def self.extra_options: (Options) -> Options
module ResponseMethods
def pushed?: () -> boolish
def mark_as_pushed!: () -> void

View File

@ -11,16 +11,12 @@ module HTTPX
interface _RetriesOptions
def retry_after: () -> Numeric?
def retry_after=: (Numeric) -> Numeric
def max_retries: () -> Integer?
def max_retries=: (int) -> Integer
def retry_change_requests: () -> bool?
def retry_change_requests=: (bool) -> bool
def retry_change_requests: () -> boolish
def retry_on: () -> _RetryCallback?
def retry_on=: (_RetryCallback) -> _RetryCallback
end
def self.extra_options: (Options) -> (Options & _RetriesOptions)

View File

@ -7,7 +7,6 @@ module HTTPX
interface _UpgradeOptions
def upgrade_handlers: () -> handlers_registry?
def upgrade_handlers=: (handlers_registry) -> handlers_registry
end
def self.extra_options: (Options) -> (Options & _UpgradeOptions)

View File

@ -107,6 +107,7 @@ class OptionsTest < Minitest::Test
:request_body_class => bar.request_body_class,
:response_body_class => bar.response_body_class,
:connection_class => bar.connection_class,
:options_class => bar.options_class,
:transport => nil,
:transport_options => nil,
:addresses => nil,

View File

@ -169,6 +169,11 @@ class SessionTest < Minitest::Test
self.class.foo
end
end
self::OptionsMethods = Module.new do
def option_foo(v)
v
end
end
def self.load_dependencies(mod)
mod.__send__(:include, Module.new do
@ -179,9 +184,7 @@ class SessionTest < Minitest::Test
end
def self.extra_options(options)
Class.new(options.class) do
def_option(:foo)
end.new(options).merge(foo: "options-foo")
options.merge(foo: "options-foo")
end
def self.configure(mod)