Skip to content

Commit eb59e38

Browse files
ghuronclaude
andcommitted
Add Item.merge() with tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 3f8b15a commit eb59e38

2 files changed

Lines changed: 85 additions & 0 deletions

File tree

src/wdpy/item.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import annotations
22
import json
33
import logging
4+
import uuid
45
from typing import Any, Dict, List, Optional
56

67
from wdpy import Statement, SourceItem, get_entities, api_write
@@ -76,6 +77,23 @@ def compress(statement: Statement) -> None:
7677
for prop, items in grouped.items():
7778
self.claims[prop] = items
7879

80+
def merge(self, statement: Statement) -> Statement:
81+
"""Upsert a statement into this item's claims.
82+
83+
If an existing statement with the same mainsnak value and compatible
84+
qualifiers is found, references are merged into it and it is returned.
85+
Otherwise the statement is appended as a new claim (with an id assigned
86+
when the item already has a qid) and returned.
87+
"""
88+
self._ensure_loaded()
89+
bucket = self.claims.setdefault(statement.mainsnak.property, [])
90+
if (existing := statement.upsert(bucket)) is None:
91+
if self.qid:
92+
statement.id = f'{self.qid}${uuid.uuid4()}'
93+
bucket.append(statement)
94+
return statement
95+
return existing
96+
7997
def json(self) -> str:
8098
data: Dict[str, Any] = {}
8199
if self.qid:

tests/test_item.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,73 @@ def test_write_creates_item(self, api_write_mock, *_):
8080
self.assertEqual('item', kwargs.get('new'))
8181
self.assertIn('data', kwargs)
8282

83+
class Merge(unittest.TestCase):
84+
85+
def setUp(self):
86+
self.item = wdpy.Item('Q1')
87+
self.item._loaded = True
88+
89+
def _s(self, val='Q5', prop='P31'):
90+
return wdpy.Statement(wdpy.Snak(prop, (val,)))
91+
92+
# ── insert ────────────────────────────────────────────────────────────────
93+
94+
def test_new_property_bucket_created(self):
95+
s = self._s()
96+
self.item.merge(s)
97+
self.assertIn('P31', self.item.claims)
98+
99+
def test_new_statement_appended(self):
100+
s = self._s()
101+
result = self.item.merge(s)
102+
self.assertIn(s, self.item.claims['P31'])
103+
self.assertIs(result, s)
104+
105+
def test_id_assigned_when_qid_known(self):
106+
self.item.merge(self._s())
107+
self.assertTrue(self.item.claims['P31'][0].id.startswith('Q1$'))
108+
109+
def test_no_id_assigned_without_qid(self):
110+
self.item.qid = None
111+
s = self._s()
112+
self.item.merge(s)
113+
self.assertIsNone(s.id)
114+
115+
def test_second_distinct_value_also_inserted(self):
116+
self.item.merge(self._s('Q5'))
117+
self.item.merge(self._s('Q6'))
118+
self.assertEqual(len(self.item.claims['P31']), 2)
119+
120+
def test_multiple_properties_independent(self):
121+
self.item.merge(self._s('Q5', 'P31'))
122+
self.item.merge(self._s('Q5', 'P21'))
123+
self.assertEqual(len(self.item.claims['P31']), 1)
124+
self.assertEqual(len(self.item.claims['P21']), 1)
125+
126+
# ── upsert ────────────────────────────────────────────────────────────────
127+
128+
def test_duplicate_returns_existing(self):
129+
existing = self._s()
130+
self.item.claims['P31'] = [existing]
131+
result = self.item.merge(self._s())
132+
self.assertIs(result, existing)
133+
self.assertEqual(len(self.item.claims['P31']), 1)
134+
135+
def test_duplicate_does_not_overwrite_existing_id(self):
136+
existing = self._s()
137+
existing.id = 'Q1$original'
138+
self.item.claims['P31'] = [existing]
139+
self.item.merge(self._s())
140+
self.assertEqual(self.item.claims['P31'][0].id, 'Q1$original')
141+
142+
# ── _ensure_loaded called ─────────────────────────────────────────────────
143+
144+
@patch('wdpy.item.get_entities', return_value={})
145+
def test_triggers_load_if_not_loaded(self, mock_get):
146+
item = wdpy.Item('Q99')
147+
item.merge(self._s())
148+
mock_get.assert_called_once()
149+
83150
@patch('wdpy.item.api_write', return_value={'entity': {'id': 'Q7'}})
84151
def test_write_updates_existing_item(self, api_write_mock, *_):
85152
item = wdpy.Item('Q7')

0 commit comments

Comments
 (0)