paquito 0.3.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d00c743e791d4a17131842b2f515d58cff0e62190c8509666aff3d0d34e2d005
4
- data.tar.gz: b6760e6c8ceefa78e77f6b7c1a5668d40930c0d80fb8f841ea0a3deda82a77dd
3
+ metadata.gz: dd3a891430b40fa7fad9d50b8f383070cee22cba06d19c0db67670324c575613
4
+ data.tar.gz: 752e27818f5781652fc91407b51a7f5c16e0a8a07baa0f1285b3e4a3c9029bb0
5
5
  SHA512:
6
- metadata.gz: 189792644f661fd6a5455921928ff94f911d972c971590ff6754bee2f0e27c3386199d0444286e279d6b658d755c3ad0e9deff2e6338620c5a1b81efe542365a
7
- data.tar.gz: c4f9cb2d28a8598627497bf3e7f8c1de1a29e2fa0a37e54e06310af438fdd5f34b6c77c533a90b121559d5e77353b5fe316a8099b4d1f8b588873363edb43c3a
6
+ metadata.gz: 7b25933ab48ec3327e7e2c781f6502f90ed7a440163e58c3255d43bb1473fc5da5414b44d85f06de7f0d90ef05d57871bd8fcb3aabd22caa19d97df3422240d2
7
+ data.tar.gz: aca4aa6e5d417d40926d9cf57045157d1eaffdae225cc051b97ad861e0dc2d19fecf1f6acdea1a9fa91982697cee231326d410a21b0c130f327fc21f967c914e
@@ -3,6 +3,21 @@ name: CI
3
3
  on: [push, pull_request]
4
4
 
5
5
  jobs:
6
+ rubocop:
7
+ runs-on: ubuntu-latest
8
+ strategy:
9
+ fail-fast: false
10
+ steps:
11
+ - name: Checkout
12
+ uses: actions/checkout@v2
13
+ - name: Set up Ruby
14
+ uses: ruby/setup-ruby@v1
15
+ with:
16
+ ruby-version: '2.7'
17
+ bundler-cache: true
18
+ - name: Run test
19
+ run: bundle exec rubocop
20
+
6
21
  rubies:
7
22
  runs-on: ubuntu-latest
8
23
  strategy:
data/Gemfile CHANGED
@@ -5,10 +5,13 @@ source "https://rubygems.org"
5
5
  # Specify your gem's dependencies in paquito.gemspec
6
6
  gemspec
7
7
 
8
+ gem "msgpack", github: "msgpack/msgpack-ruby"
9
+
8
10
  gem "rake", "~> 13.0"
9
11
  gem "activesupport", ">= 7.0.0"
10
12
  gem "activerecord", ">= 7.0.0"
11
13
  gem "sqlite3"
14
+ gem "benchmark-ips"
12
15
 
13
16
  gem "minitest", "~> 5.0"
14
17
 
data/Gemfile.lock CHANGED
@@ -1,51 +1,57 @@
1
+ GIT
2
+ remote: https://github.com/msgpack/msgpack-ruby.git
3
+ revision: b69e6d6965ef46890885ddf73f02b820f3185d80
4
+ specs:
5
+ msgpack (1.4.5)
6
+
1
7
  PATH
2
8
  remote: .
3
9
  specs:
4
- paquito (0.3.1)
10
+ paquito (0.4.0)
5
11
  msgpack
6
12
 
7
13
  GEM
8
14
  remote: https://rubygems.org/
9
15
  specs:
10
- activemodel (7.0.1)
11
- activesupport (= 7.0.1)
12
- activerecord (7.0.1)
13
- activemodel (= 7.0.1)
14
- activesupport (= 7.0.1)
15
- activesupport (7.0.1)
16
+ activemodel (7.0.2.3)
17
+ activesupport (= 7.0.2.3)
18
+ activerecord (7.0.2.3)
19
+ activemodel (= 7.0.2.3)
20
+ activesupport (= 7.0.2.3)
21
+ activesupport (7.0.2.3)
16
22
  concurrent-ruby (~> 1.0, >= 1.0.2)
17
23
  i18n (>= 1.6, < 2)
18
24
  minitest (>= 5.1)
19
25
  tzinfo (~> 2.0)
20
26
  ast (2.4.2)
27
+ benchmark-ips (2.10.0)
21
28
  byebug (11.1.3)
22
29
  concurrent-ruby (1.1.9)
23
- i18n (1.9.1)
30
+ i18n (1.10.0)
24
31
  concurrent-ruby (~> 1.0)
25
- minitest (5.14.4)
26
- msgpack (1.4.4)
32
+ minitest (5.15.0)
27
33
  parallel (1.21.0)
28
- parser (3.0.2.0)
34
+ parser (3.1.1.0)
29
35
  ast (~> 2.4.1)
30
- rainbow (3.0.0)
36
+ rainbow (3.1.1)
31
37
  rake (13.0.6)
32
- regexp_parser (2.1.1)
38
+ regexp_parser (2.2.1)
33
39
  rexml (3.2.5)
34
- rubocop (1.22.1)
40
+ rubocop (1.25.1)
35
41
  parallel (~> 1.10)
36
- parser (>= 3.0.0.0)
42
+ parser (>= 3.1.0.0)
37
43
  rainbow (>= 2.2.2, < 4.0)
38
44
  regexp_parser (>= 1.8, < 3.0)
39
45
  rexml
40
- rubocop-ast (>= 1.12.0, < 2.0)
46
+ rubocop-ast (>= 1.15.1, < 2.0)
41
47
  ruby-progressbar (~> 1.7)
42
48
  unicode-display_width (>= 1.4.0, < 3.0)
43
- rubocop-ast (1.12.0)
44
- parser (>= 3.0.1.1)
45
- rubocop-shopify (2.3.0)
46
- rubocop (~> 1.22)
49
+ rubocop-ast (1.16.0)
50
+ parser (>= 3.1.1.0)
51
+ rubocop-shopify (2.5.0)
52
+ rubocop (~> 1.25)
47
53
  ruby-progressbar (1.11.0)
48
- sorbet-runtime (0.5.9209)
54
+ sorbet-runtime (0.5.9742)
49
55
  sqlite3 (1.4.2)
50
56
  tzinfo (2.0.4)
51
57
  concurrent-ruby (~> 1.0)
@@ -59,8 +65,10 @@ PLATFORMS
59
65
  DEPENDENCIES
60
66
  activerecord (>= 7.0.0)
61
67
  activesupport (>= 7.0.0)
68
+ benchmark-ips
62
69
  byebug
63
70
  minitest (~> 5.0)
71
+ msgpack!
64
72
  paquito!
65
73
  rake (~> 13.0)
66
74
  rubocop
data/README.md CHANGED
@@ -22,7 +22,7 @@ Or install it yourself as:
22
22
 
23
23
  ### `chain`
24
24
 
25
- `Paquito::CoderChain` allows to combine two or more serializers into one.
25
+ `Paquito::CoderChain` combines two or more serializers into one.
26
26
 
27
27
  Example:
28
28
 
@@ -34,7 +34,7 @@ compressed_yaml_coder.load(payload) # => { foo: 42 }
34
34
 
35
35
  ### `ConditionalCompressor`
36
36
 
37
- `Paquito::ConditionalCompressor` compress payloads if they are over a defined size.
37
+ `Paquito::ConditionalCompressor` compresses payloads if they are over a defined size.
38
38
 
39
39
  Example:
40
40
  ```ruby
@@ -45,8 +45,8 @@ coder.dump("foo" * 500) # => "\x01<compressed-data....>"
45
45
 
46
46
  ### `SingleBytePrefixVersion`
47
47
 
48
- `Paquito::SingleBytePrefixVersion` prepends a version prefix to the payloads, which then allow to seemlessly transition from
49
- different serialization methods.
48
+ `Paquito::SingleBytePrefixVersion` prepends a version prefix to the payloads, which allows you to seamlessly transition
49
+ between different serialization methods.
50
50
 
51
51
  The first argument is the current version used for newly generated payloads.
52
52
 
@@ -64,9 +64,9 @@ coder.load("\x00---\n:foo: 42") # => { foo: 42 }
64
64
 
65
65
  ### `CommentPrefixVersion`
66
66
 
67
- Similar to the single byte prefix, but meant to be human readable and to allow for migrating unversionned payloads.
67
+ Similar to the single byte prefix, but meant to be human readable and to allow for migrating unversioned payloads.
68
68
 
69
- Payload without a version prefix are assumed to be version `0`.
69
+ Payloads without a version prefix are assumed to be version `0`.
70
70
 
71
71
  The first argument is the current version used for newly generated payloads.
72
72
 
@@ -84,7 +84,7 @@ coder.dump([1]) # => "#☠1☢\n[1]"
84
84
 
85
85
  ### `allow_nil`
86
86
 
87
- In some situation where you'd rather not serialize `nil`, you can use the `Paquito.allow_nil` shorthand:
87
+ In some situations where you'd rather not serialize `nil`, you can use the `Paquito.allow_nil` shorthand:
88
88
 
89
89
  ```ruby
90
90
  coder = Paquito.allow_nil(Marshal)
@@ -95,7 +95,7 @@ coder.load(nil) # => nil
95
95
  ### `TranslateErrors`
96
96
 
97
97
  If you do need to handle serialization or deserialization errors, for instance to fallback to acting like a cache miss,
98
- `Paquito::TranslateErrors` translate all underlying exceptions into `Paquito::Error` descendants.
98
+ `Paquito::TranslateErrors` translates all underlying exceptions into `Paquito::Error` descendants.
99
99
 
100
100
  Example:
101
101
 
@@ -117,7 +117,8 @@ coder.load(coder.dump(%i(foo bar).to_set)) # => #<Set: {:foo, :bar}>
117
117
 
118
118
  ### `TypedStruct`
119
119
 
120
- `Paquito::TypedStruct` is a opt-in Sorbet runtime plugin that allows `T::Struct` classes to be serializable. You need to explicitly include the module in the `T::Struct` classes that you will be serializing.
120
+ `Paquito::TypedStruct` is a opt-in Sorbet runtime plugin that allows `T::Struct` classes to be serializable. You need
121
+ to explicitly include the module in the `T::Struct` classes that you will be serializing.
121
122
 
122
123
  Example
123
124
 
@@ -137,12 +138,12 @@ MyStruct.from_pack([26450, "foo", 1]) # => <MyStruct bar=1, foo="foo">
137
138
 
138
139
  ## Rails utilities
139
140
 
140
- `paquito` doesn't not depend on `rails` nor any of its components, however it does provide some optional utilities for it.
141
+ `paquito` doesn't depend on `rails` or any of its components, however it does provide some optional utilities.
141
142
 
142
143
  ### `CacheEntryCoder`
143
144
 
144
- `Paquito::CacheEntryCoder` turns an `ActiveSupport::Cache::Entry` instance into a simple `Array` instance. This allows to
145
- implement custom coders for `ActiveSupport::Cache`.
145
+ `Paquito::CacheEntryCoder` turns an `ActiveSupport::Cache::Entry` instance into a simple `Array` instance. This allows
146
+ you to implement custom coders for `ActiveSupport::Cache`.
146
147
 
147
148
  Example:
148
149
 
@@ -152,7 +153,7 @@ ActiveSupport::Cache::FileStore.new("tmp/cache", coder: Paquito.chain(Paquito::C
152
153
 
153
154
  ### `SerializedColumn`
154
155
 
155
- `Paquito::SerializedColumn` allows to decorate any encoder to behave like Rails's builtin `YAMLColumn`
156
+ `Paquito::SerializedColumn` allows you to decorate any encoder to behave like Rails's builtin `YAMLColumn`
156
157
 
157
158
  Example:
158
159
 
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "paquito"
6
+ require "benchmark/ips"
7
+
8
+ BASELINE = Paquito::CodecFactory.build([Symbol], pool: false)
9
+ POOLED = Paquito::CodecFactory.build([Symbol], pool: 1)
10
+
11
+ PAYLOAD = BASELINE.dump(:foo)
12
+ MARSHAL_PAYLOAD = Marshal.dump(:foo)
13
+
14
+ Benchmark.ips do |x|
15
+ x.report("marshal") { Marshal.load(MARSHAL_PAYLOAD) }
16
+ x.report("msgpack") { BASELINE.load(PAYLOAD) }
17
+ x.report("pooled") { POOLED.load(PAYLOAD) }
18
+ x.compare!(order: :baseline)
19
+ end
@@ -8,11 +8,13 @@ module Paquito
8
8
 
9
9
  def dump(object)
10
10
  return nil if object.nil?
11
+
11
12
  @coder.dump(object)
12
13
  end
13
14
 
14
15
  def load(payload)
15
16
  return nil if payload.nil?
17
+
16
18
  @coder.load(payload)
17
19
  end
18
20
  end
@@ -5,15 +5,24 @@ require "paquito/coder_chain"
5
5
 
6
6
  module Paquito
7
7
  class CodecFactory
8
- def self.build(types, freeze: false, serializable_type: false)
9
- factory =
10
- if types.empty? && !serializable_type
11
- MessagePack
12
- else
13
- MessagePack::Factory.new
14
- end
8
+ def self.build(types, freeze: false, serializable_type: false, pool: 1)
9
+ factory = if types.empty? && !serializable_type
10
+ MessagePack::DefaultFactory
11
+ else
12
+ MessagePack::Factory.new
13
+ end
14
+
15
15
  Types.register(factory, types) unless types.empty?
16
16
  Types.register_serializable_type(factory) if serializable_type
17
+
18
+ if pool && pool > 0 && factory.respond_to?(:pool)
19
+ if serializable_type || types.any? { |t| Types.recursive?(t) }
20
+ pool *= 2
21
+ end
22
+ factory = factory.freeze.pool(pool, freeze: freeze)
23
+ freeze = false
24
+ end
25
+
17
26
  MessagePackCodec.new(factory, freeze: freeze)
18
27
  end
19
28
 
@@ -32,7 +41,11 @@ module Paquito
32
41
  end
33
42
 
34
43
  def load(payload)
35
- @factory.load(payload, freeze: @freeze)
44
+ if @freeze
45
+ @factory.load(payload, freeze: @freeze)
46
+ else
47
+ @factory.load(payload)
48
+ end
36
49
  rescue MessagePack::UnpackError => error
37
50
  raise UnpackError, error.message
38
51
  rescue IOError => error
@@ -68,6 +68,7 @@ module Paquito
68
68
 
69
69
  def dump_coder(target)
70
70
  return unless permitted_class?(target.class)
71
+
71
72
  super
72
73
  end
73
74
 
@@ -41,6 +41,7 @@ module Paquito
41
41
  if pack_digest != digest
42
42
  raise(VersionMismatchError, "#{self} digests do not match")
43
43
  end
44
+
44
45
  new(**props.keys.zip(values).to_h)
45
46
  end
46
47
 
data/lib/paquito/types.rb CHANGED
@@ -37,10 +37,12 @@ module Paquito
37
37
  def register(klass, packer: nil, unpacker:)
38
38
  if packer
39
39
  raise ArgumentError, "packer for #{klass} already defined" if packers.key?(klass)
40
+
40
41
  packers[klass] = packer
41
42
  end
42
43
 
43
44
  raise ArgumentError, "unpacker for #{klass} already defined" if unpackers.key?(klass)
45
+
44
46
  unpackers[klass] = unpacker
45
47
 
46
48
  self
@@ -72,13 +74,13 @@ module Paquito
72
74
  # New types can be added as long as they have unique #code.
73
75
  TYPES = {
74
76
  "Symbol" => {
75
- code: 0x00,
77
+ code: 0,
76
78
  packer: Symbol.method_defined?(:name) ? :name : :to_s,
77
79
  unpacker: :to_sym,
78
80
  optimized_symbols_parsing: true,
79
81
  }.freeze,
80
82
  "Time" => {
81
- code: 0x01,
83
+ code: 1,
82
84
  packer: ->(value) do
83
85
  rational = value.utc.to_r
84
86
  [rational.numerator, rational.denominator].pack(TIME_FORMAT)
@@ -89,7 +91,7 @@ module Paquito
89
91
  end,
90
92
  }.freeze,
91
93
  "DateTime" => {
92
- code: 0x02,
94
+ code: 2,
93
95
  packer: ->(value) do
94
96
  sec = value.sec + value.sec_fraction
95
97
  offset = value.offset
@@ -129,7 +131,7 @@ module Paquito
129
131
  end,
130
132
  }.freeze,
131
133
  "Date" => {
132
- code: 0x03,
134
+ code: 3,
133
135
  packer: ->(value) do
134
136
  [value.year, value.month, value.day].pack(DATE_FORMAT)
135
137
  end,
@@ -139,28 +141,29 @@ module Paquito
139
141
  end,
140
142
  }.freeze,
141
143
  "BigDecimal" => {
142
- code: 0x04,
144
+ code: 4,
143
145
  packer: :_dump,
144
146
  unpacker: BigDecimal.method(:_load),
145
147
  }.freeze,
146
148
  # Range => { code: 0x05 }, do not recycle that code
147
149
  "ActiveRecord::Base" => {
148
- code: 0x6,
150
+ code: 6,
149
151
  packer: ->(value) { ActiveRecordPacker.dump(value) },
150
152
  unpacker: ->(value) { ActiveRecordPacker.load(value) },
151
153
  }.freeze,
152
154
  "ActiveSupport::HashWithIndifferentAccess" => {
153
- code: 0x7,
155
+ code: 7,
154
156
  packer: ->(factory, value) do
155
157
  unless value.instance_of?(ActiveSupport::HashWithIndifferentAccess)
156
158
  raise PackError.new("cannot pack HashWithIndifferentClass subclass", value)
157
159
  end
160
+
158
161
  factory.dump(value.to_h)
159
162
  end,
160
163
  unpacker: ->(factory, value) { HashWithIndifferentAccess.new(factory.load(value)) },
161
164
  },
162
165
  "ActiveSupport::TimeWithZone" => {
163
- code: 0x8,
166
+ code: 8,
164
167
  packer: ->(value) do
165
168
  [
166
169
  value.utc.to_i,
@@ -176,12 +179,27 @@ module Paquito
176
179
  end,
177
180
  },
178
181
  "Set" => {
179
- code: 0x9,
182
+ code: 9,
180
183
  packer: ->(factory, value) { factory.dump(value.to_a) },
181
184
  unpacker: ->(factory, value) { factory.load(value).to_set },
182
185
  },
183
- # Object => { code: 0x7f }, reserved for serializable Object type
184
- }.freeze
186
+ # Integer => { code: 10 }, reserved for oversized Integer
187
+ # Object => { code: 127 }, reserved for serializable Object type
188
+ }
189
+ begin
190
+ require "msgpack/bigint"
191
+
192
+ TYPES["Integer"] = {
193
+ code: 10,
194
+ packer: MessagePack::Bigint.method(:to_msgpack_ext),
195
+ unpacker: MessagePack::Bigint.method(:from_msgpack_ext),
196
+ oversized_integer_extension: true,
197
+ }
198
+ rescue LoadError
199
+ # expected on older msgpack
200
+ end
201
+
202
+ TYPES.freeze
185
203
 
186
204
  class << self
187
205
  def register(factory, types)
@@ -206,6 +224,11 @@ module Paquito
206
224
  end
207
225
  end
208
226
 
227
+ def recursive?(type)
228
+ type_attributes = TYPES.fetch(type.name)
229
+ recursive_callback?(type_attributes[:packer]) || recursive_callback?(type_attributes[:unpacker])
230
+ end
231
+
209
232
  def register_serializable_type(factory)
210
233
  factory.register_type(
211
234
  0x7f,
@@ -236,9 +259,14 @@ module Paquito
236
259
 
237
260
  private
238
261
 
262
+ def recursive_callback?(callback)
263
+ callback.respond_to?(:arity) && callback.arity != 1
264
+ end
265
+
239
266
  def curry_callback(callback, factory)
240
267
  return callback.to_proc if callback.is_a?(Symbol)
241
268
  return callback if callback.arity == 1
269
+
242
270
  callback.curry.call(factory)
243
271
  end
244
272
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Paquito
4
- VERSION = "0.3.1"
4
+ VERSION = "0.4.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: paquito
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jean Boussier
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-02-02 00:00:00.000000000 Z
11
+ date: 2022-03-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: msgpack
@@ -39,6 +39,7 @@ files:
39
39
  - LICENSE.txt
40
40
  - README.md
41
41
  - Rakefile
42
+ - benchmark/msgpack-pooling.rb
42
43
  - bin/console
43
44
  - bin/setup
44
45
  - dev.yml