lunchmoney 1.4.1 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/build_and_publish_yard_docs.yml +2 -2
  3. data/.github/workflows/ci.yml +9 -10
  4. data/.github/workflows/rbi-updater.yml +1 -1
  5. data/.github/workflows/release_pipeline.yml +1 -1
  6. data/.rubocop.yml +1 -1
  7. data/.ruby-version +1 -1
  8. data/.toys/.toys.rb +8 -0
  9. data/Gemfile +1 -1
  10. data/Gemfile.lock +76 -60
  11. data/README.md +0 -2
  12. data/SECURITY.md +151 -0
  13. data/bin/check_vcr_version +94 -0
  14. data/lib/lunchmoney/api.rb +25 -38
  15. data/lib/lunchmoney/calls/assets.rb +10 -13
  16. data/lib/lunchmoney/calls/base.rb +59 -7
  17. data/lib/lunchmoney/calls/budgets.rb +22 -25
  18. data/lib/lunchmoney/calls/categories.rb +28 -36
  19. data/lib/lunchmoney/calls/crypto.rb +7 -9
  20. data/lib/lunchmoney/calls/plaid_accounts.rb +7 -9
  21. data/lib/lunchmoney/calls/recurring_expenses.rb +4 -5
  22. data/lib/lunchmoney/calls/tags.rb +3 -4
  23. data/lib/lunchmoney/calls/transactions.rb +28 -36
  24. data/lib/lunchmoney/calls/users.rb +3 -4
  25. data/lib/lunchmoney/configuration.rb +20 -0
  26. data/lib/lunchmoney/objects/object.rb +4 -9
  27. data/lib/lunchmoney/validators.rb +8 -6
  28. data/lib/lunchmoney/version.rb +1 -1
  29. data/lib/lunchmoney.rb +3 -3
  30. data/lunchmoney.gemspec +1 -1
  31. data/sorbet/rbi/annotations/activesupport.rbi +35 -0
  32. data/sorbet/rbi/dsl/active_support/callbacks.rbi +0 -2
  33. data/sorbet/rbi/gems/{activesupport@7.2.2.1.rbi → activesupport@8.0.2.1.rbi} +1369 -989
  34. data/sorbet/rbi/gems/{ast@2.4.2.rbi → ast@2.4.3.rbi} +4 -3
  35. data/sorbet/rbi/gems/{base64@0.2.0.rbi → base64@0.3.0.rbi} +76 -39
  36. data/sorbet/rbi/gems/{benchmark@0.4.0.rbi → benchmark@0.4.1.rbi} +50 -49
  37. data/sorbet/rbi/gems/bigdecimal@3.2.2.rbi +275 -0
  38. data/sorbet/rbi/gems/{dotenv@3.1.7.rbi → dotenv@3.1.8.rbi} +9 -9
  39. data/sorbet/rbi/gems/{drb@2.2.1.rbi → drb@2.2.3.rbi} +503 -188
  40. data/sorbet/rbi/gems/{faraday-net_http@3.4.0.rbi → faraday-net_http@3.4.1.rbi} +23 -23
  41. data/sorbet/rbi/gems/{faraday@2.12.2.rbi → faraday@2.13.4.rbi} +468 -141
  42. data/sorbet/rbi/gems/{hashdiff@1.1.2.rbi → hashdiff@1.2.0.rbi} +5 -3
  43. data/sorbet/rbi/gems/{json@2.10.2.rbi → json@2.13.2.rbi} +386 -175
  44. data/sorbet/rbi/gems/{kramdown@2.4.0.rbi → kramdown@2.5.1.rbi} +316 -234
  45. data/sorbet/rbi/gems/lint_roller@1.1.0.rbi +240 -0
  46. data/sorbet/rbi/gems/{logger@1.6.6.rbi → logger@1.7.0.rbi} +63 -40
  47. data/sorbet/rbi/gems/{parser@3.3.7.1.rbi → parser@3.3.9.0.rbi} +303 -291
  48. data/sorbet/rbi/gems/{rack@3.1.9.rbi → rack@3.2.1.rbi} +712 -547
  49. data/sorbet/rbi/gems/{rake@13.2.1.rbi → rake@13.3.0.rbi} +238 -227
  50. data/sorbet/rbi/gems/{rbi@0.3.1.rbi → rbi@0.3.6.rbi} +1084 -2521
  51. data/sorbet/rbi/gems/{rbs@3.9.0.rbi → rbs@4.0.0.dev.4.rbi} +1336 -419
  52. data/sorbet/rbi/gems/{regexp_parser@2.10.0.rbi → regexp_parser@2.11.2.rbi} +1044 -956
  53. data/sorbet/rbi/gems/require-hooks@0.2.2.rbi +110 -0
  54. data/sorbet/rbi/gems/{rexml@3.4.1.rbi → rexml@3.4.2.rbi} +302 -292
  55. data/sorbet/rbi/gems/{rubocop-ast@1.38.0.rbi → rubocop-ast@1.46.0.rbi} +802 -1016
  56. data/sorbet/rbi/gems/{rubocop-minitest@0.36.0.rbi → rubocop-minitest@0.38.2.rbi} +129 -93
  57. data/sorbet/rbi/gems/{rubocop-rails@2.29.1.rbi → rubocop-rails@2.33.3.rbi} +575 -230
  58. data/sorbet/rbi/gems/{rubocop-shopify@2.15.1.rbi → rubocop-shopify@2.17.1.rbi} +1 -0
  59. data/sorbet/rbi/gems/{rubocop-sorbet@0.8.7.rbi → rubocop-sorbet@0.10.5.rbi} +761 -83
  60. data/sorbet/rbi/gems/{rubocop@1.71.2.rbi → rubocop@1.80.1.rbi} +6543 -3097
  61. data/sorbet/rbi/gems/{spoom@1.6.1.rbi → spoom@1.7.6.rbi} +1179 -2621
  62. data/sorbet/rbi/gems/{tapioca@0.16.11.rbi → tapioca@0.17.7.rbi} +623 -745
  63. data/sorbet/rbi/gems/{thor@1.3.2.rbi → thor@1.4.0.rbi} +88 -67
  64. data/sorbet/rbi/gems/{unicode-display_width@3.1.4.rbi → unicode-display_width@3.1.5.rbi} +5 -5
  65. data/sorbet/rbi/gems/{vcr@6.3.1.rbi → vcr@6.3.1-ce35c236fe48899f02ddf780973b44cdb756c0ee.rbi} +140 -123
  66. metadata +42 -38
  67. data/sorbet/rbi/gems/bigdecimal@3.1.9.rbi +0 -78
  68. /data/sorbet/rbi/gems/{connection_pool@2.5.0.rbi → connection_pool@2.5.3.rbi} +0 -0
  69. /data/sorbet/rbi/gems/{language_server-protocol@3.17.0.4.rbi → language_server-protocol@3.17.0.5.rbi} +0 -0
  70. /data/sorbet/rbi/gems/{parallel@1.26.3.rbi → parallel@1.27.0.rbi} +0 -0
@@ -59,17 +59,16 @@ module LunchMoney
59
59
  })
60
60
  response = get("transactions", query_params: params)
61
61
 
62
- api_errors = errors(response)
63
- return api_errors if api_errors.present?
62
+ handle_api_response(response) do |body|
63
+ body[:transactions].map do |transaction|
64
+ transaction[:tags].map! { |tag| LunchMoney::Objects::TagBase.new(**tag) }
64
65
 
65
- response.body[:transactions].map do |transaction|
66
- transaction[:tags].map! { |tag| LunchMoney::Objects::TagBase.new(**tag) }
66
+ transaction[:children]&.map! do |child_transaction|
67
+ LunchMoney::Objects::ChildTransaction.new(**child_transaction)
68
+ end
67
69
 
68
- transaction[:children]&.map! do |child_transaction|
69
- LunchMoney::Objects::ChildTransaction.new(**child_transaction)
70
+ LunchMoney::Objects::Transaction.new(**transaction)
70
71
  end
71
-
72
- LunchMoney::Objects::Transaction.new(**transaction)
73
72
  end
74
73
  end
75
74
 
@@ -83,10 +82,9 @@ module LunchMoney
83
82
  params = clean_params({ debit_as_negative: })
84
83
  response = get("transactions/#{transaction_id}", query_params: params)
85
84
 
86
- api_errors = errors(response)
87
- return api_errors if api_errors.present?
88
-
89
- LunchMoney::Objects::Transaction.new(**response.body)
85
+ handle_api_response(response) do |body|
86
+ LunchMoney::Objects::Transaction.new(**body)
87
+ end
90
88
  end
91
89
 
92
90
  sig do
@@ -111,10 +109,9 @@ module LunchMoney
111
109
  })
112
110
  response = post("transactions", params)
113
111
 
114
- api_errors = errors(response)
115
- return api_errors if api_errors.present?
116
-
117
- response.body
112
+ handle_api_response(response) do |body|
113
+ body
114
+ end
118
115
  end
119
116
 
120
117
  sig do
@@ -141,10 +138,9 @@ module LunchMoney
141
138
  })
142
139
  response = put("transactions/#{transaction_id}", params)
143
140
 
144
- api_errors = errors(response)
145
- return api_errors if api_errors.present?
146
-
147
- response.body
141
+ handle_api_response(response) do |body|
142
+ body
143
+ end
148
144
  end
149
145
 
150
146
  sig do
@@ -157,20 +153,18 @@ module LunchMoney
157
153
  params = { parent_ids:, remove_parents: }
158
154
  response = post("transactions/unsplit", params)
159
155
 
160
- api_errors = errors(response)
161
- return api_errors if api_errors.present?
162
-
163
- response.body
156
+ handle_api_response(response) do |body|
157
+ body
158
+ end
164
159
  end
165
160
 
166
161
  sig { params(transaction_id: Integer).returns(T.any(LunchMoney::Objects::Transaction, LunchMoney::Errors)) }
167
162
  def transaction_group(transaction_id)
168
163
  response = get("transactions/group", query_params: { transaction_id: })
169
164
 
170
- api_errors = errors(response)
171
- return api_errors if api_errors.present?
172
-
173
- LunchMoney::Objects::Transaction.new(**response.body)
165
+ handle_api_response(response) do |body|
166
+ LunchMoney::Objects::Transaction.new(**body)
167
+ end
174
168
  end
175
169
 
176
170
  sig do
@@ -194,10 +188,9 @@ module LunchMoney
194
188
  })
195
189
  response = post("transactions/group", params)
196
190
 
197
- api_errors = errors(response)
198
- return api_errors if api_errors.present?
199
-
200
- response.body
191
+ handle_api_response(response) do |body|
192
+ body
193
+ end
201
194
  end
202
195
 
203
196
  sig do
@@ -209,10 +202,9 @@ module LunchMoney
209
202
  def delete_transaction_group(transaction_id)
210
203
  response = delete("transactions/group/#{transaction_id}")
211
204
 
212
- api_errors = errors(response)
213
- return api_errors if api_errors.present?
214
-
215
- response.body
205
+ handle_api_response(response) do |body|
206
+ body
207
+ end
216
208
  end
217
209
  end
218
210
  end
@@ -11,10 +11,9 @@ module LunchMoney
11
11
  def me
12
12
  response = get("me")
13
13
 
14
- api_errors = errors(response)
15
- return api_errors if api_errors.present?
16
-
17
- LunchMoney::Objects::User.new(**response.body)
14
+ handle_api_response(response) do |body|
15
+ LunchMoney::Objects::User.new(**body)
16
+ end
18
17
  end
19
18
  end
20
19
  end
@@ -22,5 +22,25 @@ module LunchMoney
22
22
  @api_key = ENV.fetch("LUNCHMONEY_TOKEN", nil)
23
23
  @validate_object_attributes = T.let(true, T::Boolean)
24
24
  end
25
+
26
+ sig { returns(T::Array[String]) }
27
+ def validation_errors
28
+ errors = []
29
+
30
+ if api_key&.strip&.empty?
31
+ errors << "API key cannot be empty"
32
+ end
33
+
34
+ if api_key && T.must(api_key).length < 10
35
+ errors << "API key appears to be too short"
36
+ end
37
+
38
+ errors
39
+ end
40
+
41
+ sig { returns(T::Boolean) }
42
+ def valid?
43
+ validation_errors.empty?
44
+ end
25
45
  end
26
46
  end
@@ -8,17 +8,12 @@ module LunchMoney
8
8
  class Object
9
9
  sig { params(symbolize_keys: T::Boolean).returns(T::Hash[T.any(String, Symbol), T.untyped]) }
10
10
  def serialize(symbolize_keys: false)
11
- ivars = instance_variables
12
-
13
11
  output = {}
14
12
 
15
- ivars.each do |ivar|
16
- key = ivar.to_s.gsub("@", "")
17
- key = key.to_sym if symbolize_keys
18
-
19
- value = instance_variable_get(ivar)
20
-
21
- output[key] = value
13
+ instance_variables.each do |ivar|
14
+ key_string = ivar.to_s.delete_prefix("@")
15
+ key = symbolize_keys ? key_string.to_sym : key_string
16
+ output[key] = instance_variable_get(ivar)
22
17
  end
23
18
 
24
19
  output
@@ -8,9 +8,10 @@ module LunchMoney
8
8
  module Validators
9
9
  include Kernel
10
10
 
11
- sig { params(value: String, valid_values: T::Array[String]).returns(String) }
12
- def validate_one_of!(value, valid_values)
13
- return value unless LunchMoney.validate_object_attributes?
11
+ sig { params(value: String, valid_values: T::Array[String], validate: T.nilable(T::Boolean)).returns(String) }
12
+ def validate_one_of!(value, valid_values, validate: nil)
13
+ should_validate = validate.nil? ? LunchMoney.validate_object_attributes? : validate
14
+ return value unless should_validate
14
15
 
15
16
  if valid_values.exclude?(value)
16
17
  raise(InvalidObjectAttribute, "#{value} is invalid, must be one of #{valid_values.join(", ")}")
@@ -19,9 +20,10 @@ module LunchMoney
19
20
  value
20
21
  end
21
22
 
22
- sig { params(value: String).returns(String) }
23
- def validate_iso8601!(value)
24
- return value unless LunchMoney.validate_object_attributes?
23
+ sig { params(value: String, validate: T.nilable(T::Boolean)).returns(String) }
24
+ def validate_iso8601!(value, validate: nil)
25
+ should_validate = validate.nil? ? LunchMoney.validate_object_attributes? : validate
26
+ return value unless should_validate
25
27
 
26
28
  raise(InvalidObjectAttribute, "#{value} is not a valid ISO 8601 string") unless valid_iso8601_string?(value)
27
29
 
@@ -3,5 +3,5 @@
3
3
 
4
4
  module LunchMoney
5
5
  # Current version of the gem
6
- VERSION = "1.4.1"
6
+ VERSION = "1.5.0"
7
7
  end
data/lib/lunchmoney.rb CHANGED
@@ -1,9 +1,9 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
- require "active_support"
5
- require "active_support/core_ext/module/delegation.rb"
6
- require "active_support/core_ext/object/blank.rb"
4
+ require "active_support/core_ext/module/delegation"
5
+ require "active_support/core_ext/object/blank"
6
+ require "active_support/core_ext/module/attribute_accessors"
7
7
  require "faraday"
8
8
  require "sorbet-runtime"
9
9
 
data/lunchmoney.gemspec CHANGED
@@ -9,7 +9,7 @@ Gem::Specification.new do |spec|
9
9
 
10
10
  spec.summary = "LunchMoney API client library."
11
11
  spec.homepage = "https://github.com/mmenanno/lunchmoney"
12
- spec.required_ruby_version = ">= 3.1"
12
+ spec.required_ruby_version = ">= 3.2"
13
13
  spec.license = "MIT"
14
14
 
15
15
  spec.metadata["allowed_push_host"] = "https://rubygems.org"
@@ -88,6 +88,10 @@ class Hash
88
88
 
89
89
  sig { returns(T::Boolean) }
90
90
  def extractable_options?; end
91
+
92
+ # @version >= 6.1.0
93
+ sig { returns(T::Hash[K, V]) }
94
+ def compact_blank; end
91
95
  end
92
96
 
93
97
  class Array
@@ -193,6 +197,32 @@ class DateTime
193
197
  def present?; end
194
198
  end
195
199
 
200
+ module Enumerable
201
+ sig { type_parameters(:Block).params(block: T.proc.params(arg0: Elem).returns(T.type_parameter(:Block))).returns(T::Hash[T.type_parameter(:Block), Elem]) }
202
+ sig { returns(T::Enumerable[T.untyped]) }
203
+ def index_by(&block); end
204
+
205
+ sig { type_parameters(:Block).params(block: T.proc.params(arg0: Elem).returns(T.type_parameter(:Block))).returns(T::Hash[Elem, T.type_parameter(:Block)]) }
206
+ sig { returns(T::Enumerable[T.untyped]) }
207
+ sig { type_parameters(:Default).params(default: T.type_parameter(:Default)).returns(T::Hash[Elem, T.type_parameter(:Default)]) }
208
+ def index_with(default = nil, &block); end
209
+
210
+ sig { params(block: T.proc.params(arg0: Elem).returns(BasicObject)).returns(T::Boolean) }
211
+ sig { returns(T::Boolean) }
212
+ def many?(&block); end
213
+
214
+ sig { params(object: BasicObject).returns(T::Boolean) }
215
+ def exclude?(object); end
216
+
217
+ # @version >= 6.1.0
218
+ sig { returns(T::Array[Elem]) }
219
+ def compact_blank; end
220
+
221
+ # @version >= 7.0.0
222
+ sig { returns(Elem) }
223
+ def sole; end
224
+ end
225
+
196
226
  class NilClass
197
227
  sig { returns(TrueClass) }
198
228
  def blank?; end
@@ -455,3 +485,8 @@ class ActiveSupport::ErrorReporter
455
485
  sig { params(error: T.any(Exception, String), severity: T.nilable(Symbol), context: T::Hash[Symbol, T.untyped], source: T.nilable(String)).void }
456
486
  def unexpected(error, severity: T.unsafe(nil), context: T.unsafe(nil), source: T.unsafe(nil)); end
457
487
  end
488
+
489
+ module ActiveSupport::Testing::Assertions
490
+ sig { type_parameters(:Block).params(block: T.proc.returns(T.type_parameter(:Block))).returns(T.type_parameter(:Block)) }
491
+ def assert_nothing_raised(&block); end
492
+ end
@@ -13,11 +13,9 @@ module ActiveSupport::Callbacks
13
13
  module GeneratedClassMethods
14
14
  def __callbacks; end
15
15
  def __callbacks=(value); end
16
- def __callbacks?; end
17
16
  end
18
17
 
19
18
  module GeneratedInstanceMethods
20
19
  def __callbacks; end
21
- def __callbacks?; end
22
20
  end
23
21
  end