lutaml-model 0.8.49 → 0.8.50

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: 2dab1dcbc2ea77cfd1c61d9ffba4717521b9e97e1f3f6c8dd6f9ce6c7014222f
4
- data.tar.gz: ed16efadd94ce85b9e52a5c7de822f9c7de7e3c581203280120630e8cdb389ff
3
+ metadata.gz: f2f88962ec380d731bd7a31ce5ff62f9aec9dcef4b7783aebfb128b6b5c86bbb
4
+ data.tar.gz: b282edc8ccf91c2381315e58a34d54a15642153e673ba31bbbd46c084c7634c4
5
5
  SHA512:
6
- metadata.gz: cd6bbdebb2c812eb849774ac168fe2c194bc21113521be6b4ca8b389ab5f6ebb2743b951478077332a18a2c214f458962c0a89d54e1a26447b3b103b7c2bb153
7
- data.tar.gz: fddad44bad334a640facc5f6869b91c2a12134da6c79e58b7bed1787e14c03702d5ffd0ffcece27e435fe2f9387a7d13040e45794d178424cd7e56acbf9bb36c
6
+ metadata.gz: f92f932e37bed4d355b3a41c1d2599bf23a5f0907f2ba18967f27cb5a572a4be11d080d54d0a73787055b6e9143414928e87086f2269f42acfb77665e9cccda6
7
+ data.tar.gz: a5801daa0947b0a2308fc720412d0add3cd5956f6a902cf20aa62b0ece6d883ec801dc4c4112319ab161a5d68934d54aac0253d601515823d04440df52011400
@@ -350,6 +350,59 @@ module Lutaml
350
350
  instance
351
351
  end
352
352
 
353
+ # Fast bulk constructor for deserialization-heavy callers
354
+ # (native extensions building object trees bottom-up).
355
+ #
356
+ # Allocates without running #initialize and applies each present
357
+ # attribute through its compiled writer, which casts the value
358
+ # and marks it as explicitly set — so defaults, to_hash output,
359
+ # and using_default? behave exactly as if the instance had been
360
+ # produced by from_hash. Values may be primitives or already
361
+ # built instances (instances pass through casting unchanged);
362
+ # absent keys keep their defaults. Unknown keys raise.
363
+ #
364
+ # @param attrs [Hash] attribute names (String or Symbol) to
365
+ # pre-cast values
366
+ # @param register [Symbol, nil] The register context
367
+ # @return [Object] The hydrated instance
368
+ def instantiate(attrs = {}, register = nil)
369
+ instance = allocate_for_deserialization(register)
370
+ register_id = instance.lutaml_register
371
+ given = {}
372
+ attrs.each do |key, value|
373
+ name = key.to_sym
374
+ next if name == :lutaml_register
375
+
376
+ unless attributes(register_id).key?(name)
377
+ raise Error, "unknown attribute '#{name}' for #{self}"
378
+ end
379
+
380
+ given[name] = value
381
+ end
382
+ attributes(register_id).each do |name, attr|
383
+ next if attr.derived?
384
+
385
+ if given.key?(name)
386
+ instance.public_send(:"#{name}=", given[name])
387
+ else
388
+ # Absent keys seed their default explicitly, mirroring
389
+ # initialize_attributes: every mapped attribute ends up
390
+ # set (nil when no default), never left as the
391
+ # uninitialized sentinel — readers and formatters touch
392
+ # absent attributes freely.
393
+ default = attr.default_value(register_id, instance)
394
+ value = if Lutaml::Model::Utils.uninitialized?(default)
395
+ nil
396
+ else
397
+ attr.cast_value(default, register_id)
398
+ end
399
+ instance.public_send(:"#{name}=", value)
400
+ instance.using_default_for(name)
401
+ end
402
+ end
403
+ instance
404
+ end
405
+
353
406
  # Define register-specific attribute methods on the class itself.
354
407
  #
355
408
  # Called once per (class, register) combination. Replaces per-instance
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Lutaml
4
4
  module Model
5
- VERSION = "0.8.49"
5
+ VERSION = "0.8.50"
6
6
  end
7
7
  end
@@ -30,7 +30,11 @@ module Lutaml
30
30
 
31
31
  child_kwargs[name] = []
32
32
  end
33
- instance = model_class.new(**attr_kwargs, **child_kwargs)
33
+ # TODO.max-perf/33: the bulk constructor — allocate +
34
+ # compiled writers, from_hash semantics — measured ~30x on
35
+ # hydration-heavy corpora versus the generic constructor.
36
+ instance = model_class.instantiate(attr_kwargs.merge(child_kwargs),
37
+ register)
34
38
  instance.lutaml_parent = parent if parent
35
39
  instance.lutaml_root ||= parent&.lutaml_root || parent
36
40
  instance.element_order = PlanOrder.build(node) if node && plan[:ordered]
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+ require_relative "../../../lib/lutaml/model"
5
+
6
+ module InstantiateMapping
7
+ class Part < Lutaml::Model::Serializable
8
+ attribute :label, :string
9
+ attribute :count, :integer
10
+ end
11
+
12
+ class Widget < Lutaml::Model::Serializable
13
+ attribute :name, :string
14
+ attribute :flag, :boolean
15
+ attribute :part, Part
16
+ attribute :tags, :string, collection: true
17
+
18
+ key_value do
19
+ map "name", to: :name
20
+ map "flag", to: :flag
21
+ map "part", to: :part
22
+ map "tags", to: :tags
23
+ end
24
+ end
25
+ end
26
+
27
+ RSpec.describe "Serializable.instantiate" do
28
+ it "hydrates attributes from primitives" do
29
+ widget = InstantiateMapping::Widget.instantiate("name" => "w", "flag" => true,
30
+ "tags" => %w[a b])
31
+ expect(widget.name).to eq("w")
32
+ expect(widget.flag).to be(true)
33
+ expect(widget.tags).to eq(%w[a b])
34
+ end
35
+
36
+ it "accepts pre-built instances for typed attributes" do
37
+ part = InstantiateMapping::Part.instantiate(label: "p", count: 2)
38
+ widget = InstantiateMapping::Widget.instantiate(name: "w", part: part)
39
+
40
+ expect(widget.part).to equal(part)
41
+ end
42
+
43
+ it "keeps defaults for absent keys and reports them as defaults" do
44
+ widget = InstantiateMapping::Widget.instantiate
45
+ # Absent scalars read as the uninitialized sentinel, exactly like the
46
+ # XML fast path; serialization omits them either way.
47
+ expect(Lutaml::Model::Utils.uninitialized?(widget.name) ||
48
+ widget.name.nil?).to be(true)
49
+ expect(widget).to be_using_default(:name)
50
+ end
51
+
52
+ it "marks set attributes as explicitly set" do
53
+ widget = InstantiateMapping::Widget.instantiate(name: "w")
54
+ expect(widget).not_to be_using_default(:name)
55
+ end
56
+
57
+ it "serializes identically to from_hash" do
58
+ input = { "name" => "w", "flag" => false,
59
+ "part" => { "label" => "p", "count" => 3 },
60
+ "tags" => ["x"] }
61
+ from_hash = InstantiateMapping::Widget.from_hash(input)
62
+
63
+ part = InstantiateMapping::Part.instantiate(input["part"])
64
+ fast = InstantiateMapping::Widget.instantiate(
65
+ name: "w", flag: false, part: part, tags: ["x"],
66
+ )
67
+
68
+ expect(fast.to_hash).to eq(from_hash.to_hash)
69
+ end
70
+
71
+ it "raises on unknown attribute names" do
72
+ expect { InstantiateMapping::Widget.instantiate(bogus: 1) }
73
+ .to raise_error(Lutaml::Model::Error, /unknown attribute/)
74
+ end
75
+ 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.49
4
+ version: 0.8.50
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
@@ -1797,6 +1797,7 @@ files:
1797
1797
  - spec/lutaml/model/schema/yaml_schema_spec.rb
1798
1798
  - spec/lutaml/model/sequence_choice_compositor_spec.rb
1799
1799
  - spec/lutaml/model/sequence_spec.rb
1800
+ - spec/lutaml/model/serializable_instantiate_spec.rb
1800
1801
  - spec/lutaml/model/serializable_spec.rb
1801
1802
  - spec/lutaml/model/serializable_validation_spec.rb
1802
1803
  - spec/lutaml/model/serialize/builder_spec.rb