cdek_api 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 59e95212ed17015e114c28f4fd91691991f0f679827194410fdf8b9cc348952c
4
+ data.tar.gz: 4547c0e0b9ae640f752992b2928804eca7dc20aaea359a0f6a31addc3bc9c86f
5
+ SHA512:
6
+ metadata.gz: a306eb3956070db989b66acf817295fd85155031e22425e5fd53f8538c016e32a68bd8b0d52e79ee0b6ea4e7076bdab1ac69cbb9651369248cd244836443de4b
7
+ data.tar.gz: 54a79eea3eb1bfcd23991a78fab76ec54828c67f49bc361140922294e29fd3af76ece81cd6fc4c9dd849d38ebf7ca5352a9f7454622efb9d29331829e7979caa
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2021 Pavel Osetrov
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,77 @@
1
+ # СДЭК АПИ
2
+
3
+ API wrapper для СДЭК [API](https://api-docs.cdek.ru/29923741.html).
4
+
5
+ ## Установка Ruby
6
+
7
+ $ gem install cdek_api
8
+
9
+ ## Установка Rails
10
+
11
+ добавьте в Gemfile:
12
+
13
+ gem 'cdek_api'
14
+
15
+ и запустите `bundle install`.
16
+
17
+ Затем:
18
+
19
+ rails g cdek_api:install
20
+
21
+ ## Требования
22
+
23
+ Необходимо запросить CLIENT_ID и CLIENT_SECRET для production
24
+
25
+ ## Использование Rails
26
+
27
+ В файл `config/cdek_api.yml` вставьте ваши данные
28
+
29
+ ## Использование Ruby
30
+
31
+ Сначала cгенерируйте access_token
32
+ Затем создайте экземпляр объекта `CdekApi::Request`:
33
+
34
+ ```ruby
35
+ access_token = CdekApi.generate_access_token('EMscd6r9JnFiQ3bLoyjJY6eM78JrJceI', 'PjLZkKBHEiLK3YsjtNrt3TGNG0ahs3kG', 'client_credentials')
36
+ delivery = CdekApi::Request.new(access_token: access_token)
37
+ ```
38
+
39
+ Вы можете изменять `access_token`, `timeout`, `open_timeout`, `faraday_adapter`, `proxy`, `symbolize_keys`, `logger`, и `debug`:
40
+
41
+ ```ruby
42
+ CdekApi::Request.access_token = "your_access_token"
43
+ CdekApi::Request.timeout = 15
44
+ CdekApi::Request.open_timeout = 15
45
+ CdekApi::Request.symbolize_keys = true
46
+ CdekApi::Request.debug = false
47
+ ```
48
+
49
+ Либо в файле `config/initializers/cdek_api.rb` для Rails.
50
+
51
+ ## Debug Logging
52
+
53
+ Pass `debug: true` to enable debug logging to STDOUT.
54
+
55
+ ```ruby
56
+ delivery = CdekApi::Request.new(access_token: "your_access_token", debug: true)
57
+ ```
58
+
59
+ ### Custom logger
60
+
61
+ Ruby `Logger.new` is used by default, but it can be overrided using:
62
+
63
+ ```ruby
64
+ delivery = CdekApi::Request.new(access_token: "your_access_token", debug: true, logger: MyLogger.new)
65
+ ```
66
+
67
+ Logger can be also set by globally:
68
+
69
+ ```ruby
70
+ CdekApi::Request.logger = MyLogger.new
71
+ ```
72
+
73
+ ## Примеры
74
+
75
+ ```ruby
76
+ CdekApi::Request.location.regions.retrieve.body
77
+ ```
@@ -0,0 +1,218 @@
1
+ module CdekApi
2
+ class APIRequest
3
+
4
+ def initialize(builder: nil)
5
+ @request_builder = builder
6
+ end
7
+
8
+ def post(params: nil, headers: nil, body: {}, first_time: true)
9
+ validate_access_token
10
+
11
+ begin
12
+ response = self.rest_client.post do |request|
13
+ configure_request(request: request, params: params, headers: headers, body: MultiJson.dump(body))
14
+ end
15
+ parse_response(response)
16
+ rescue StandardError => e
17
+ if e.response.try(:dig, :status) == 401 && first_time
18
+ CdekApi::Request.access_token = CdekApi.generate_access_token.try(:dig, "access_token")
19
+ self.post(params: params, headers: headers, body: body, first_time: false)
20
+ else
21
+ handle_error(e)
22
+ end
23
+ end
24
+ end
25
+
26
+ def patch(params: nil, headers: nil, body: {}, first_time: true)
27
+ validate_access_token
28
+
29
+ begin
30
+ response = self.rest_client.patch do |request|
31
+ body[:senderId] = CdekApi.senders.first["id"] if body[:senderId].nil?
32
+ configure_request(request: request, params: params, headers: headers, body: MultiJson.dump(body))
33
+ end
34
+ parse_response(response)
35
+ rescue StandardError => e
36
+ if e.response.dig(:status) == 401 && first_time
37
+ CdekApi::Request.access_token = CdekApi.generate_access_token.try(:dig, "access_token")
38
+ self.patch(params: params, headers: headers, body: body, first_time: false)
39
+ else
40
+ handle_error(e)
41
+ end
42
+ end
43
+ end
44
+
45
+ def put(params: nil, headers: nil, body: {}, first_time: true)
46
+ validate_access_token
47
+
48
+ begin
49
+ response = self.rest_client.put do |request|
50
+ configure_request(request: request, params: params, headers: headers, body: MultiJson.dump(body))
51
+ end
52
+ parse_response(response)
53
+ rescue StandardError => e
54
+ if e.response.dig(:status) == 401 && first_time
55
+ CdekApi::Request.access_token = CdekApi.generate_access_token.try(:dig, "access_token")
56
+ self.put(params: params, headers: headers, body: body, first_time: false)
57
+ else
58
+ handle_error(e)
59
+ end
60
+ end
61
+ end
62
+
63
+ def get(params: nil, headers: nil, first_time: true)
64
+ validate_access_token
65
+
66
+ begin
67
+ response = self.rest_client.get do |request|
68
+ configure_request(request: request, params: params, headers: headers)
69
+ end
70
+ parse_response(response)
71
+ rescue StandardError => e
72
+ if e.response.dig(:status) == 401 && first_time
73
+ CdekApi::Request.access_token = CdekApi.generate_access_token.try(:dig, "access_token")
74
+ self.get(params: params, headers: headers, first_time: false)
75
+ else
76
+ handle_error(e)
77
+ end
78
+ end
79
+ end
80
+
81
+ def delete(params: nil, headers: nil, first_time: true)
82
+ validate_access_token
83
+
84
+ begin
85
+ response = self.rest_client.delete do |request|
86
+ configure_request(request: request, params: params, headers: headers)
87
+ end
88
+ parse_response(response)
89
+ rescue StandardError => e
90
+ if e.response.dig(:status) == 401 && first_time
91
+ CdekApi::Request.access_token = CdekApi.generate_access_token.try(:dig, "access_token")
92
+ self.delete(params: params, headers: headers, first_time: false)
93
+ else
94
+ handle_error(e)
95
+ end
96
+ end
97
+ end
98
+
99
+ protected
100
+
101
+ # Convenience accessors
102
+
103
+ def access_token
104
+ @request_builder.access_token
105
+ end
106
+
107
+ def api_endpoint
108
+ @request_builder.api_endpoint
109
+ end
110
+
111
+ def timeout
112
+ @request_builder.timeout
113
+ end
114
+
115
+ def open_timeout
116
+ @request_builder.open_timeout
117
+ end
118
+
119
+ def proxy
120
+ @request_builder.proxy
121
+ end
122
+
123
+ def adapter
124
+ @request_builder.faraday_adapter
125
+ end
126
+
127
+ def symbolize_keys
128
+ @request_builder.symbolize_keys
129
+ end
130
+
131
+ # Helpers
132
+
133
+ def handle_error(error)
134
+ error_params = {}
135
+
136
+ begin
137
+ if error.is_a?(Faraday::ClientError) && error.response
138
+ error_params[:status_code] = error.response[:status]
139
+ error_params[:raw_body] = error.response[:body]
140
+
141
+ parsed_response = MultiJson.load(error.response[:body], symbolize_keys: symbolize_keys)
142
+
143
+ if parsed_response
144
+ error_params[:body] = parsed_response
145
+
146
+ title_key = symbolize_keys ? :title : "title"
147
+ detail_key = symbolize_keys ? :detail : "detail"
148
+
149
+ error_params[:title] = parsed_response[title_key] if parsed_response[title_key]
150
+ error_params[:detail] = parsed_response[detail_key] if parsed_response[detail_key]
151
+ end
152
+
153
+ end
154
+ rescue MultiJson::ParseError
155
+ end
156
+
157
+ error_to_raise = Error.new(error.message, error_params)
158
+
159
+ raise error_to_raise
160
+ end
161
+
162
+ def configure_request(request: nil, params: nil, headers: nil, body: nil)
163
+ if request
164
+ request.params.merge!(params) if params
165
+ request.headers['Content-Type'] = 'application/json'
166
+ request.headers['Authorization'] = "Bearer #{CdekApi::Request.access_token}"
167
+ request.headers['User-Agent'] = "CdekApi/#{CdekApi::VERSION} Ruby gem"
168
+ request.headers.merge!(headers) if headers
169
+ request.body = body if body
170
+ request.options.timeout = self.timeout
171
+ request.options.open_timeout = self.open_timeout
172
+ end
173
+ end
174
+
175
+ def rest_client
176
+ client = Faraday.new(self.api_url, proxy: self.proxy, ssl: { version: "TLSv1_2" }) do |faraday|
177
+ faraday.response :raise_error
178
+ faraday.adapter adapter
179
+ if @request_builder.debug
180
+ faraday.response :logger, @request_builder.logger, bodies: true
181
+ end
182
+ end
183
+ client
184
+ end
185
+
186
+ def parse_response(response)
187
+ parsed_response = nil
188
+
189
+ if response.body && !response.body.empty?
190
+ begin
191
+ headers = response.headers
192
+ body = MultiJson.load(response.body, symbolize_keys: symbolize_keys)
193
+ parsed_response = Response.new(headers: headers, body: body)
194
+ rescue MultiJson::ParseError
195
+ error_params = { title: "UNPARSEABLE_RESPONSE", status_code: 500 }
196
+ error = Error.new("Unparseable response: #{response.body}", error_params)
197
+ raise error
198
+ end
199
+ end
200
+
201
+ parsed_response
202
+ end
203
+
204
+ def validate_access_token
205
+ unless self.access_token
206
+ raise CdekApi::Error, "You must set an access_token prior to making a call"
207
+ end
208
+ end
209
+
210
+ def api_url
211
+ base_api_url + @request_builder.path
212
+ end
213
+
214
+ def base_api_url
215
+ "#{CdekApi.host}/v2/"
216
+ end
217
+ end
218
+ end
@@ -0,0 +1,29 @@
1
+ module CdekApi
2
+ class Error < StandardError
3
+ attr_reader :title, :detail, :body, :raw_body, :status_code
4
+
5
+ def initialize(message = "", params = {})
6
+ @title = params[:title]
7
+ @detail = params[:detail]
8
+ @body = params[:body]
9
+ @raw_body = params[:raw_body]
10
+ @status_code = params[:status_code]
11
+
12
+ super(message)
13
+ end
14
+
15
+ def to_s
16
+ super + " " + instance_variables_to_s
17
+ end
18
+
19
+ private
20
+
21
+ def instance_variables_to_s
22
+ [:title, :detail, :body, :raw_body, :status_code].map do |attr|
23
+ attr_value = send(attr)
24
+
25
+ "@#{attr}=#{attr_value.inspect}"
26
+ end.join(", ")
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,94 @@
1
+ module CdekApi
2
+ class Request
3
+ attr_accessor :access_token, :api_endpoint, :timeout, :open_timeout, :proxy, :faraday_adapter, :symbolize_keys, :debug, :logger, :test
4
+
5
+ DEFAULT_TIMEOUT = 60
6
+ DEFAULT_OPEN_TIMEOUT = 60
7
+
8
+ def initialize(access_token: nil, api_endpoint: nil, timeout: nil, open_timeout: nil, proxy: nil, faraday_adapter: nil, symbolize_keys: false, debug: false, logger: nil, test: false)
9
+ @path_parts = []
10
+ @access_token = access_token || self.class.access_token || CdekApi.generate_access_token.try(:dig, "access_token")
11
+ @access_token = @access_token.strip if @access_token
12
+ @api_endpoint = api_endpoint || self.class.api_endpoint
13
+ @timeout = timeout || self.class.timeout || DEFAULT_TIMEOUT
14
+ @open_timeout = open_timeout || self.class.open_timeout || DEFAULT_OPEN_TIMEOUT
15
+ @proxy = proxy || self.class.proxy || ENV['CDEK_PROXY']
16
+ @faraday_adapter = faraday_adapter || self.class.faraday_adapter || Faraday.default_adapter
17
+ @symbolize_keys = symbolize_keys || self.class.symbolize_keys || false
18
+ @debug = debug || self.class.debug || false
19
+ @test = test || self.class.test || false
20
+ @logger = logger || self.class.logger || ::Logger.new(STDOUT)
21
+ end
22
+
23
+ def method_missing(method, *args)
24
+ @path_parts << method.to_s
25
+ @path_parts << args if args.length > 0
26
+ @path_parts.flatten!
27
+ self
28
+ end
29
+
30
+ def respond_to_missing?(method_name, include_private = false)
31
+ true
32
+ end
33
+
34
+ def send(*args)
35
+ if args.length == 0
36
+ method_missing(:send, args)
37
+ else
38
+ __send__(*args)
39
+ end
40
+ end
41
+
42
+ def path
43
+ @path_parts.join('/')
44
+ end
45
+
46
+ def create(params: nil, headers: nil, body: {})
47
+ APIRequest.new(builder: self).post(params: params, headers: headers, body: body)
48
+ ensure
49
+ reset
50
+ end
51
+
52
+ def update(params: nil, headers: nil, body: {})
53
+ APIRequest.new(builder: self).patch(params: params, headers: headers, body: body)
54
+ ensure
55
+ reset
56
+ end
57
+
58
+ def upsert(params: nil, headers: nil, body: {})
59
+ APIRequest.new(builder: self).put(params: params, headers: headers, body: body)
60
+ ensure
61
+ reset
62
+ end
63
+
64
+ def retrieve(params: nil, headers: nil)
65
+ APIRequest.new(builder: self).get(params: params, headers: headers)
66
+ ensure
67
+ reset
68
+ end
69
+
70
+ def delete(params: nil, headers: nil)
71
+ APIRequest.new(builder: self).delete(params: params, headers: headers)
72
+ ensure
73
+ reset
74
+ end
75
+
76
+ protected
77
+
78
+ def reset
79
+ @path_parts = []
80
+ end
81
+
82
+ class << self
83
+ attr_accessor :access_token, :timeout, :open_timeout, :api_endpoint, :proxy, :faraday_adapter, :symbolize_keys, :debug, :logger, :test
84
+
85
+ def method_missing(sym, *args, &block)
86
+ new(access_token: self.access_token, api_endpoint: self.api_endpoint, timeout: self.timeout, open_timeout: self.open_timeout, faraday_adapter: self.faraday_adapter, symbolize_keys: self.symbolize_keys, debug: self.debug, proxy: self.proxy, logger: self.logger, test: self.test).send(sym, *args, &block)
87
+ end
88
+
89
+ def respond_to_missing?(method_name, include_private = false)
90
+ true
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,10 @@
1
+ module CdekApi
2
+ class Response
3
+ attr_accessor :body, :headers
4
+
5
+ def initialize(body: {}, headers: {})
6
+ @body = body
7
+ @headers = headers
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,3 @@
1
+ module CdekApi
2
+ VERSION = "0.0.1"
3
+ end
data/lib/cdek_api.rb ADDED
@@ -0,0 +1,60 @@
1
+ require 'faraday'
2
+ require 'multi_json'
3
+ require 'cdek_api/error'
4
+ require 'cdek_api/request'
5
+ require 'cdek_api/api_request'
6
+ require 'cdek_api/response'
7
+ require 'cdek_api/version'
8
+
9
+ module CdekApi
10
+ class << self
11
+ def generate_access_token(client_id=CdekApi.client_id, client_secret=CdekApi.client_secret, grant_type=CdekApi.grant_type)
12
+ response = Faraday.post(CdekApi.url_token, "grant_type=#{grant_type}&client_id=#{client_id}&client_secret=#{client_secret}")
13
+ JSON.parse(response.body)
14
+ end
15
+
16
+ def setup
17
+ yield self
18
+ end
19
+
20
+ def register(name, value, type = nil)
21
+ cattr_accessor "#{name}_setting".to_sym
22
+
23
+ add_reader(name)
24
+ add_writer(name, type)
25
+ send "#{name}=", value
26
+ end
27
+
28
+ def add_reader(name)
29
+ define_singleton_method(name) do |*args|
30
+ send("#{name}_setting").value(*args)
31
+ end
32
+ end
33
+
34
+ def add_writer(name, type)
35
+ define_singleton_method("#{name}=") do |value|
36
+ send("#{name}_setting=", DynamicSetting.build(value, type))
37
+ end
38
+ end
39
+ end
40
+
41
+ class DynamicSetting
42
+ def self.build(setting, type)
43
+ (type ? klass(type) : self).new(setting)
44
+ end
45
+
46
+ def self.klass(type)
47
+ klass = "#{type.to_s.camelcase}Setting"
48
+ raise ArgumentError, "Unknown type: #{type}" unless CdekApi.const_defined?(klass)
49
+ CdekApi.const_get(klass)
50
+ end
51
+
52
+ def initialize(setting)
53
+ @setting = setting
54
+ end
55
+
56
+ def value(*_args)
57
+ @setting
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,2 @@
1
+ Description:
2
+ cdek_api:install
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ module CdekApi
4
+ class InstallGenerator < Rails::Generators::Base
5
+ source_root File.expand_path('templates', __dir__)
6
+
7
+ def generate_install
8
+ copy_file 'cdek_api.yml', 'config/cdek_api.yml'
9
+ copy_file 'cdek_api.rb', 'config/initializers/cdek_api.rb'
10
+ end
11
+ end
12
+ end
13
+
@@ -0,0 +1,17 @@
1
+ require 'cdek_api'
2
+
3
+ CdekApi.setup do |config|
4
+ if File.exist?('config/cdek_api.yml')
5
+ processed = YAML.load_file('config/cdek_api.yml')[Rails.env]
6
+
7
+ processed.each do |k, v|
8
+ config::register k.underscore.to_sym, v
9
+ end
10
+
11
+ config::Request.access_token = ENV['CDEK_ACCESS_TOKEN'] || CdekApi.generate_access_token.try(:dig, "access_token")
12
+ config::Request.timeout = 15
13
+ config::Request.open_timeout = 15
14
+ config::Request.symbolize_keys = true
15
+ config::Request.debug = false
16
+ end
17
+ end
@@ -0,0 +1,16 @@
1
+ defaults: &defaults
2
+ GRANT_TYPE: 'client_credentials'
3
+ CLIENT_ID: 'EMscd6r9JnFiQ3bLoyjJY6eM78JrJceI'
4
+ CLIENT_SECRET: 'PjLZkKBHEiLK3YsjtNrt3TGNG0ahs3kG'
5
+ URL_TOKEN: 'https://api.edu.cdek.ru/v2/oauth/token?parameters'
6
+ HOST: 'https://api.edu.cdek.ru'
7
+ production:
8
+ <<: *defaults
9
+ URL_TOKEN: 'https://api.cdek.ru/v2/oauth/token?parameters'
10
+ HOST: 'https://api.cdek.ru'
11
+ CLIENT_ID: ''
12
+ CLIENT_SECRET: ''
13
+ development:
14
+ <<: *defaults
15
+ test:
16
+ <<: *defaults
metadata ADDED
@@ -0,0 +1,82 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cdek_api
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Pavel Osetrov
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2022-08-03 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: faraday
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 0.16.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 0.16.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: multi_json
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 1.11.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 1.11.0
41
+ description: ''
42
+ email: pavel.osetrov@me.com
43
+ executables: []
44
+ extensions: []
45
+ extra_rdoc_files: []
46
+ files:
47
+ - LICENSE
48
+ - README.markdown
49
+ - lib/cdek_api.rb
50
+ - lib/cdek_api/api_request.rb
51
+ - lib/cdek_api/error.rb
52
+ - lib/cdek_api/request.rb
53
+ - lib/cdek_api/response.rb
54
+ - lib/cdek_api/version.rb
55
+ - lib/generators/cdek_api/install/USAGE
56
+ - lib/generators/cdek_api/install/install_generator.rb
57
+ - lib/generators/cdek_api/install/templates/cdek_api.rb
58
+ - lib/generators/cdek_api/install/templates/cdek_api.yml
59
+ homepage: https://github.com/osetrov/cdek_api
60
+ licenses:
61
+ - MIT
62
+ metadata: {}
63
+ post_install_message:
64
+ rdoc_options: []
65
+ require_paths:
66
+ - lib
67
+ required_ruby_version: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ version: '2.5'
72
+ required_rubygems_version: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ requirements: []
78
+ rubygems_version: 3.2.32
79
+ signing_key:
80
+ specification_version: 4
81
+ summary: CDEK API
82
+ test_files: []