lutaml-model 0.8.67 → 0.8.69

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 52c9d5f90fb80dbbec116f4a6e8d65e92d8b5fe3181a9a4b7aa26349b64d8938
4
- data.tar.gz: 657ef667d7d9192a6f6d6592168c8bf5c3a3f25aeff8fa4127d4438954ac7bf0
3
+ metadata.gz: f8ea7edf1c5b0a9d5e208fb6a83df7f40eba0a83e9ac78797130269e1a3cba7c
4
+ data.tar.gz: 7f2d97eac672eae74a1a192db0196305d62c45d3bd1f34855fb30a79475d6f88
5
5
  SHA512:
6
- metadata.gz: 14797b4e4855ac206622648c2ae5cf01b5115e9a243f461861d90e78903a80df356d35144b024801dd9556ed3d89fe526069157faf5bceb804088829be768be4
7
- data.tar.gz: 179a58214dc093680917167055d3f3deec644303b310c91763d27c6aed18b19fc1e5668dea0c8a1a1e3542ef54b8d39712ade45a869ee0c072f1bb92ae10c18f
6
+ metadata.gz: e202fb8a65018521903d42867ecd434df1f84c8568eab99f0f4e737651b6ecb51e91fc60c9b188aecc8e65f645bb10f9103a73b70617356a05c9d7ba566c8c8a
7
+ data.tar.gz: cabf97f1c9aaf7432a4831d3c5b5fec4d54306a073f9deb4e1e2cb4bf7ad97b608b52775cfef0b73294c51fd794368531c521386bb55a84ac2c1c23ad4004e56
@@ -83,6 +83,13 @@ module Lutaml
83
83
  def root_mapping?
84
84
  name == "root_mapping"
85
85
  end
86
+
87
+ # The writer symbol for this rule's target, interned once per
88
+ # rule: the interpolation allocated a String and interned it per
89
+ # row per document in the transform's hot loop.
90
+ def kv_setter
91
+ @kv_setter ||= :"#{to}="
92
+ end
86
93
  end
87
94
  end
88
95
  end
@@ -1,5 +1,6 @@
1
1
  module Lutaml
2
2
  module KeyValue
3
+ MISSING = Object.new.freeze
3
4
  class Transform < Lutaml::Model::Transform
4
5
  def data_to_model(data, format, options = {})
5
6
  # Use child's own default register if it has one
@@ -208,14 +209,17 @@ module Lutaml
208
209
  children = []
209
210
  absent = []
210
211
  rows.each do |rule, attr, kind, type, child_rows|
211
- unless Lutaml::Model::Utils.string_or_symbol_key?(doc, rule.name)
212
+ # One hash pass per row: fetch with a sentinel distinguishes
213
+ # absent keys from present-nil values (both read as nil), so
214
+ # the separate string_or_symbol_key? probe goes away.
215
+ v = Lutaml::Model::Utils.fetch_str_or_sym(doc, rule.name, MISSING)
216
+ if v.equal?(MISSING)
212
217
  absent << rule
213
218
  next
214
219
  end
215
- v = Lutaml::Model::Utils.fetch_str_or_sym(doc, rule.name)
216
220
 
217
221
  if kind == :scalar
218
- setters << [:"#{rule.to}=", v]
222
+ setters << [rule.kv_setter, v]
219
223
  next
220
224
  end
221
225
 
@@ -239,7 +243,7 @@ module Lutaml
239
243
 
240
244
  built << child
241
245
  end
242
- setters << [:"#{rule.to}=", built]
246
+ setters << [rule.kv_setter, built]
243
247
  children.concat(built)
244
248
  else
245
249
  return nil unless v.is_a?(::Hash)
@@ -249,7 +253,7 @@ module Lutaml
249
253
  options)
250
254
  return nil if child.nil?
251
255
 
252
- setters << [:"#{rule.to}=", child]
256
+ setters << [rule.kv_setter, child]
253
257
  children << child
254
258
  end
255
259
  end
@@ -151,8 +151,19 @@ model_class: nil)
151
151
  return value.public_send(:"to_#{format}")
152
152
  end
153
153
 
154
- # Wrap value in type and call to_#{format}
155
154
  if rule.attribute_type.is_a?(Class) && rule.attribute_type < Lutaml::Model::Type::Value
155
+ plan = serialize_plan(rule)
156
+ # Identity only under identity_fast_type's exact condition
157
+ # (no custom to AND no custom from): a custom from means the
158
+ # wrapper constructor normalizes the value, so the raw value
159
+ # must still pass through it.
160
+ if plan[:identity_type] && value.instance_of?(plan[:identity_type])
161
+ return value
162
+ end
163
+ if (to_fn = plan[:to_fn])
164
+ return to_fn.call(rule.attribute_type.new(value))
165
+ end
166
+
156
167
  wrapped_value = rule.attribute_type.new(value)
157
168
  wrapped_value.public_send(:"to_#{format}")
158
169
  else
@@ -187,9 +198,23 @@ model_class: nil)
187
198
  reference: reference_type?(rule),
188
199
  union: ::Lutaml::Model::Type::Union.rule?(rule),
189
200
  nested: nested_model?(rule),
201
+ # The custom to_<format> serializer, resolved once per rule:
202
+ # the default path is pure identity (to_<format> returns the
203
+ # wrapped .value), so values without a custom serializer skip
204
+ # the wrap-allocate-cast ceremony entirely, and custom ones
205
+ # skip the per-value registry lookup.
206
+ to_fn: custom_to_fn(rule),
190
207
  }
191
208
  end
192
209
 
210
+ def custom_to_fn(rule)
211
+ type = rule.attribute_type
212
+ return nil unless type.is_a?(::Class) && type < ::Lutaml::Model::Type::Value
213
+
214
+ ::Lutaml::Model::Type::Value
215
+ .format_type_serializer_for(@format, type)&.fetch(:to, nil)
216
+ end
217
+
193
218
  # Builtin scalars whose type registers no custom to_<format>
194
219
  # serializer serialize as the value itself — Type::Value#to_<format>'s
195
220
  # default — without the wrap-allocate-cast ceremony per field.
@@ -316,6 +316,16 @@ module Lutaml
316
316
  @unresolved_type || @type
317
317
  end
318
318
 
319
+ def reference_type?
320
+ (@reference_type ||= unresolved_type == Lutaml::Model::Type::Reference)
321
+ end
322
+
323
+ def value_map_boolean?
324
+ (@value_map_boolean ||=
325
+ type == Lutaml::Model::Type::Boolean ||
326
+ unresolved_type == Lutaml::Model::Type::Boolean)
327
+ end
328
+
319
329
  def polymorphic?
320
330
  @options[:polymorphic_class]
321
331
  end
@@ -438,8 +448,7 @@ module Lutaml
438
448
 
439
449
  option = value_map[key]
440
450
  if (option.is_a?(TrueClass) || option.is_a?(FalseClass)) &&
441
- (type == Lutaml::Model::Type::Boolean ||
442
- unresolved_type == Lutaml::Model::Type::Boolean)
451
+ value_map_boolean?
443
452
  return option
444
453
  end
445
454
 
@@ -507,7 +516,8 @@ module Lutaml
507
516
  return resolved_type.new(value) if value.is_a?(::Hash) && !hash_type?
508
517
 
509
518
  # Special handling for Reference types - pass the metadata
510
- if unresolved_type == Lutaml::Model::Type::Reference
519
+ # (the comparison is attribute-constant; memoize the verdict)
520
+ if reference_type?
511
521
  return resolved_type.cast_with_metadata(value,
512
522
  @options[:ref_model_class], @options[:ref_key_attribute])
513
523
  end
@@ -12,6 +12,14 @@ module Lutaml
12
12
  # options are normalized into value_map entries at MappingRule construction
13
13
  # time, so downstream code never needs to re-interpret them.
14
14
  module RenderPolicy
15
+ # Isolated holder: host transformations freeze after compile; an
16
+ # ivar cache on them raises FrozenError (learned the hard way).
17
+ SKIP_PLANS = ::Class.new do
18
+ def self.cache
19
+ @cache ||= {}.compare_by_identity
20
+ end
21
+ end.cache
22
+
15
23
  def self.derived_attribute_for?(context_obj, attr_name)
16
24
  return false unless context_obj.is_a?(Lutaml::Model::Serialize) &&
17
25
  context_obj.class.is_a?(Class) &&
@@ -52,16 +60,26 @@ module Lutaml
52
60
  # @param context_obj [Object] The context object (model_instance or delegate_obj)
53
61
  # @return [Boolean] true if should skip
54
62
  def check_skip_logic?(value, rule, context_obj)
55
- attr_name = extract_attribute_name(rule)
56
-
57
- to_map = to_value_map(rule)
58
-
59
- case value
60
- when nil
63
+ # Rule-constant parts (attribute name, :to value map) are frozen
64
+ # per rule; recomputing them per value showed in the to_yaml
65
+ # wall profile. The host transformation freezes itself after
66
+ # compile, so the cache lives in an isolated holder (TypeProbeCache
67
+ # precedent): host object -> identity-keyed rule plans.
68
+ per_host = (SKIP_PLANS[self] ||= {}.compare_by_identity)
69
+ skip_plan = (per_host[rule] ||= [extract_attribute_name(rule), to_value_map(rule)])
70
+ attr_name, to_map = skip_plan
71
+
72
+ # Plain conditionals, not `when ->(v)` literals: each lambda
73
+ # literal in a case/when allocates on every call — two per
74
+ # value here, 16% of to_yaml's allocations in the object-mode
75
+ # profile.
76
+ if value.nil?
61
77
  return to_map[:nil] == :omitted
62
- when ->(v) { Lutaml::Model::Utils.empty?(v) }
78
+ end
79
+ if Lutaml::Model::Utils.empty?(value)
63
80
  return to_map[:empty] == :omitted
64
- when ->(v) { Lutaml::Model::Utils.uninitialized?(v) }
81
+ end
82
+ if Lutaml::Model::Utils.uninitialized?(value)
65
83
  return to_map[:omitted] == :omitted || to_map[:omitted].nil?
66
84
  end
67
85
 
@@ -67,12 +67,14 @@ from: nil)
67
67
  # @param type_class [Class] the type class to look up
68
68
  # @return [Hash, nil] { to: Proc, from: Proc } or nil
69
69
  def format_type_serializer_for(format, type_class)
70
- # Hot path: memoize per [format, resolved class] — the hierarchy
71
- # walk below only runs on a miss. Format serializers register at
72
- # load time, so a hit is stable.
73
- cache = SERIALIZER_LOOKUP_CACHE
74
- key = [format, type_class]
75
- return cache[key] if cache.key?(key)
70
+ # Hot path: memoized per format -> class with NO key
71
+ # allocation — the previous [format, class] array key
72
+ # allocated on every lookup (2k arrays per 200 serializes in
73
+ # the object-mode profile). Identity-keyed nested maps; the
74
+ # hierarchy walk below only runs on a miss, and format
75
+ # serializers register at load time, so a hit is stable.
76
+ per_format = (SERIALIZER_LOOKUP_CACHE[format] ||= {}.compare_by_identity)
77
+ return per_format[type_class] if per_format.key?(type_class)
76
78
 
77
79
  klass = type_class
78
80
  found = nil
@@ -85,7 +87,7 @@ from: nil)
85
87
 
86
88
  klass = klass.superclass
87
89
  end
88
- cache[key] = found
90
+ per_format[type_class] = found
89
91
  found
90
92
  end
91
93
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Lutaml
4
4
  module Model
5
- VERSION = "0.8.67"
5
+ VERSION = "0.8.69"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lutaml-model
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.67
4
+ version: 0.8.69
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.