record_test.py 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878
  1. import pickle
  2. import datetime
  3. import pytest
  4. import uuid
  5. from pyrsistent import (
  6. PRecord, field, InvariantException, ny, pset, PSet, CheckedPVector,
  7. PTypeError, pset_field, pvector_field, pmap_field, pmap, PMap,
  8. pvector, PVector, v, m)
  9. class ARecord(PRecord):
  10. x = field(type=(int, float))
  11. y = field()
  12. class Hierarchy(PRecord):
  13. point1 = field(ARecord)
  14. point2 = field(ARecord)
  15. points = pvector_field(ARecord)
  16. class RecordContainingContainers(PRecord):
  17. map = pmap_field(str, str)
  18. vec = pvector_field(str)
  19. set = pset_field(str)
  20. class UniqueThing(PRecord):
  21. id = field(type=uuid.UUID, factory=uuid.UUID)
  22. class Something(object):
  23. pass
  24. class Another(object):
  25. pass
  26. def test_create_ignore_extra_true():
  27. h = Hierarchy.create(
  28. {'point1': {'x': 1, 'y': 'foo', 'extra_field_0': 'extra_data_0'},
  29. 'point2': {'x': 1, 'y': 'foo', 'extra_field_1': 'extra_data_1'},
  30. 'extra_field_2': 'extra_data_2',
  31. }, ignore_extra=True
  32. )
  33. assert h
  34. def test_create_ignore_extra_true_sequence_hierarchy():
  35. h = Hierarchy.create(
  36. {'point1': {'x': 1, 'y': 'foo', 'extra_field_0': 'extra_data_0'},
  37. 'point2': {'x': 1, 'y': 'foo', 'extra_field_1': 'extra_data_1'},
  38. 'points': [{'x': 1, 'y': 'foo', 'extra_field_2': 'extra_data_2'},
  39. {'x': 1, 'y': 'foo', 'extra_field_3': 'extra_data_3'}],
  40. 'extra_field____': 'extra_data_2',
  41. }, ignore_extra=True
  42. )
  43. assert h
  44. def test_ignore_extra_for_pvector_field():
  45. class HierarchyA(PRecord):
  46. points = pvector_field(ARecord, optional=False)
  47. class HierarchyB(PRecord):
  48. points = pvector_field(ARecord, optional=True)
  49. point_object = {'x': 1, 'y': 'foo', 'extra_field': 69}
  50. h = HierarchyA.create({'points': [point_object]}, ignore_extra=True)
  51. assert h
  52. h = HierarchyB.create({'points': [point_object]}, ignore_extra=True)
  53. assert h
  54. def test_create():
  55. r = ARecord(x=1, y='foo')
  56. assert r.x == 1
  57. assert r.y == 'foo'
  58. assert isinstance(r, ARecord)
  59. def test_create_ignore_extra():
  60. r = ARecord.create({'x': 1, 'y': 'foo', 'z': None}, ignore_extra=True)
  61. assert r.x == 1
  62. assert r.y == 'foo'
  63. assert isinstance(r, ARecord)
  64. def test_create_ignore_extra_false():
  65. with pytest.raises(AttributeError):
  66. _ = ARecord.create({'x': 1, 'y': 'foo', 'z': None})
  67. def test_correct_assignment():
  68. r = ARecord(x=1, y='foo')
  69. r2 = r.set('x', 2.0)
  70. r3 = r2.set('y', 'bar')
  71. assert r2 == {'x': 2.0, 'y': 'foo'}
  72. assert r3 == {'x': 2.0, 'y': 'bar'}
  73. assert isinstance(r3, ARecord)
  74. def test_direct_assignment_not_possible():
  75. with pytest.raises(AttributeError):
  76. ARecord().x = 1
  77. def test_cannot_assign_undeclared_fields():
  78. with pytest.raises(AttributeError):
  79. ARecord().set('z', 5)
  80. def test_cannot_assign_wrong_type_to_fields():
  81. try:
  82. ARecord().set('x', 'foo')
  83. assert False
  84. except PTypeError as e:
  85. assert e.source_class == ARecord
  86. assert e.field == 'x'
  87. assert e.expected_types == set([int, float])
  88. assert e.actual_type is type('foo')
  89. def test_cannot_construct_with_undeclared_fields():
  90. with pytest.raises(AttributeError):
  91. ARecord(z=5)
  92. def test_cannot_construct_with_fields_of_wrong_type():
  93. with pytest.raises(TypeError):
  94. ARecord(x='foo')
  95. def test_support_record_inheritance():
  96. class BRecord(ARecord):
  97. z = field()
  98. r = BRecord(x=1, y='foo', z='bar')
  99. assert isinstance(r, BRecord)
  100. assert isinstance(r, ARecord)
  101. assert r == {'x': 1, 'y': 'foo', 'z': 'bar'}
  102. def test_single_type_spec():
  103. class A(PRecord):
  104. x = field(type=int)
  105. r = A(x=1)
  106. assert r.x == 1
  107. with pytest.raises(TypeError):
  108. r.set('x', 'foo')
  109. def test_remove():
  110. r = ARecord(x=1, y='foo')
  111. r2 = r.remove('y')
  112. assert isinstance(r2, ARecord)
  113. assert r2 == {'x': 1}
  114. def test_remove_non_existing_member():
  115. r = ARecord(x=1, y='foo')
  116. with pytest.raises(KeyError):
  117. r.remove('z')
  118. def test_field_invariant_must_hold():
  119. class BRecord(PRecord):
  120. x = field(invariant=lambda x: (x > 1, 'x too small'))
  121. y = field(mandatory=True)
  122. try:
  123. BRecord(x=1)
  124. assert False
  125. except InvariantException as e:
  126. assert e.invariant_errors == ('x too small',)
  127. assert e.missing_fields == ('BRecord.y',)
  128. def test_global_invariant_must_hold():
  129. class BRecord(PRecord):
  130. __invariant__ = lambda r: (r.x <= r.y, 'y smaller than x')
  131. x = field()
  132. y = field()
  133. BRecord(x=1, y=2)
  134. try:
  135. BRecord(x=2, y=1)
  136. assert False
  137. except InvariantException as e:
  138. assert e.invariant_errors == ('y smaller than x',)
  139. assert e.missing_fields == ()
  140. def test_set_multiple_fields():
  141. a = ARecord(x=1, y='foo')
  142. b = a.set(x=2, y='bar')
  143. assert b == {'x': 2, 'y': 'bar'}
  144. def test_initial_value():
  145. class BRecord(PRecord):
  146. x = field(initial=1)
  147. y = field(initial=2)
  148. a = BRecord()
  149. assert a.x == 1
  150. assert a.y == 2
  151. def test_enum_field():
  152. try:
  153. from enum import Enum
  154. except ImportError:
  155. return # Enum not supported in this environment
  156. class ExampleEnum(Enum):
  157. x = 1
  158. y = 2
  159. class RecordContainingEnum(PRecord):
  160. enum_field = field(type=ExampleEnum)
  161. r = RecordContainingEnum(enum_field=ExampleEnum.x)
  162. assert r.enum_field == ExampleEnum.x
  163. def test_type_specification_must_be_a_type():
  164. with pytest.raises(TypeError):
  165. class BRecord(PRecord):
  166. x = field(type=1)
  167. def test_initial_must_be_of_correct_type():
  168. with pytest.raises(TypeError):
  169. class BRecord(PRecord):
  170. x = field(type=int, initial='foo')
  171. def test_invariant_must_be_callable():
  172. with pytest.raises(TypeError):
  173. class BRecord(PRecord):
  174. x = field(invariant='foo') # type: ignore
  175. def test_global_invariants_are_inherited():
  176. class BRecord(PRecord):
  177. __invariant__ = lambda r: (r.x % r.y == 0, 'modulo')
  178. x = field()
  179. y = field()
  180. class CRecord(BRecord):
  181. __invariant__ = lambda r: (r.x > r.y, 'size')
  182. try:
  183. CRecord(x=5, y=3)
  184. assert False
  185. except InvariantException as e:
  186. assert e.invariant_errors == ('modulo',)
  187. def test_global_invariants_must_be_callable():
  188. with pytest.raises(TypeError):
  189. class CRecord(PRecord):
  190. __invariant__ = 1
  191. def test_repr():
  192. r = ARecord(x=1, y=2)
  193. assert repr(r) == 'ARecord(x=1, y=2)' or repr(r) == 'ARecord(y=2, x=1)'
  194. def test_factory():
  195. class BRecord(PRecord):
  196. x = field(type=int, factory=int)
  197. assert BRecord(x=2.5) == {'x': 2}
  198. def test_factory_must_be_callable():
  199. with pytest.raises(TypeError):
  200. class BRecord(PRecord):
  201. x = field(type=int, factory=1) # type: ignore
  202. def test_nested_record_construction():
  203. class BRecord(PRecord):
  204. x = field(int, factory=int)
  205. class CRecord(PRecord):
  206. a = field()
  207. b = field(type=BRecord)
  208. r = CRecord.create({'a': 'foo', 'b': {'x': '5'}})
  209. assert isinstance(r, CRecord)
  210. assert isinstance(r.b, BRecord)
  211. assert r == {'a': 'foo', 'b': {'x': 5}}
  212. def test_pickling():
  213. x = ARecord(x=2.0, y='bar')
  214. y = pickle.loads(pickle.dumps(x, -1))
  215. assert x == y
  216. assert isinstance(y, ARecord)
  217. def test_supports_pickling_with_typed_container_fields():
  218. obj = RecordContainingContainers(
  219. map={'foo': 'bar'}, set=['hello', 'there'], vec=['a', 'b'])
  220. obj2 = pickle.loads(pickle.dumps(obj))
  221. assert obj == obj2
  222. def test_all_invariant_errors_reported():
  223. class BRecord(PRecord):
  224. x = field(factory=int, invariant=lambda x: (x >= 0, 'x negative'))
  225. y = field(mandatory=True)
  226. class CRecord(PRecord):
  227. a = field(invariant=lambda x: (x != 0, 'a zero'))
  228. b = field(type=BRecord)
  229. try:
  230. CRecord.create({'a': 0, 'b': {'x': -5}})
  231. assert False
  232. except InvariantException as e:
  233. assert set(e.invariant_errors) == set(['x negative', 'a zero'])
  234. assert e.missing_fields == ('BRecord.y',)
  235. def test_precord_factory_method_is_idempotent():
  236. class BRecord(PRecord):
  237. x = field()
  238. y = field()
  239. r = BRecord(x=1, y=2)
  240. assert BRecord.create(r) is r
  241. def test_serialize():
  242. class BRecord(PRecord):
  243. d = field(type=datetime.date,
  244. factory=lambda d: datetime.datetime.strptime(d, "%d%m%Y").date(),
  245. serializer=lambda format, d: d.strftime('%Y-%m-%d') if format == 'ISO' else d.strftime('%d%m%Y'))
  246. assert BRecord(d='14012015').serialize('ISO') == {'d': '2015-01-14'}
  247. assert BRecord(d='14012015').serialize('other') == {'d': '14012015'}
  248. def test_nested_serialize():
  249. class BRecord(PRecord):
  250. d = field(serializer=lambda format, d: format)
  251. class CRecord(PRecord):
  252. b = field()
  253. serialized = CRecord(b=BRecord(d='foo')).serialize('bar')
  254. assert serialized == {'b': {'d': 'bar'}}
  255. assert isinstance(serialized, dict)
  256. def test_serializer_must_be_callable():
  257. with pytest.raises(TypeError):
  258. class CRecord(PRecord):
  259. x = field(serializer=1) # type: ignore
  260. def test_transform_without_update_returns_same_precord():
  261. r = ARecord(x=2.0, y='bar')
  262. assert r.transform([ny], lambda x: x) is r
  263. class Application(PRecord):
  264. name = field(type=str)
  265. image = field(type=str)
  266. class ApplicationVector(CheckedPVector):
  267. __type__ = Application
  268. class Node(PRecord):
  269. applications = field(type=ApplicationVector)
  270. def test_nested_create_serialize():
  271. node = Node(applications=[Application(name='myapp', image='myimage'),
  272. Application(name='b', image='c')])
  273. node2 = Node.create({'applications': [{'name': 'myapp', 'image': 'myimage'},
  274. {'name': 'b', 'image': 'c'}]})
  275. assert node == node2
  276. serialized = node.serialize()
  277. restored = Node.create(serialized)
  278. assert restored == node
  279. def test_pset_field_initial_value():
  280. """
  281. ``pset_field`` results in initial value that is empty.
  282. """
  283. class Record(PRecord):
  284. value = pset_field(int)
  285. assert Record() == Record(value=[])
  286. def test_pset_field_custom_initial():
  287. """
  288. A custom initial value can be passed in.
  289. """
  290. class Record(PRecord):
  291. value = pset_field(int, initial=(1, 2))
  292. assert Record() == Record(value=[1, 2])
  293. def test_pset_field_factory():
  294. """
  295. ``pset_field`` has a factory that creates a ``PSet``.
  296. """
  297. class Record(PRecord):
  298. value = pset_field(int)
  299. record = Record(value=[1, 2])
  300. assert isinstance(record.value, PSet)
  301. def test_pset_field_checked_set():
  302. """
  303. ``pset_field`` results in a set that enforces its type.
  304. """
  305. class Record(PRecord):
  306. value = pset_field(int)
  307. record = Record(value=[1, 2])
  308. with pytest.raises(TypeError):
  309. record.value.add("hello") # type: ignore
  310. def test_pset_field_checked_vector_multiple_types():
  311. """
  312. ``pset_field`` results in a vector that enforces its types.
  313. """
  314. class Record(PRecord):
  315. value = pset_field((int, str))
  316. record = Record(value=[1, 2, "hello"])
  317. with pytest.raises(TypeError):
  318. record.value.add(object())
  319. def test_pset_field_type():
  320. """
  321. ``pset_field`` enforces its type.
  322. """
  323. class Record(PRecord):
  324. value = pset_field(int)
  325. record = Record()
  326. with pytest.raises(TypeError):
  327. record.set("value", None)
  328. def test_pset_field_mandatory():
  329. """
  330. ``pset_field`` is a mandatory field.
  331. """
  332. class Record(PRecord):
  333. value = pset_field(int)
  334. record = Record(value=[1])
  335. with pytest.raises(InvariantException):
  336. record.remove("value")
  337. def test_pset_field_default_non_optional():
  338. """
  339. By default ``pset_field`` is non-optional, i.e. does not allow
  340. ``None``.
  341. """
  342. class Record(PRecord):
  343. value = pset_field(int)
  344. with pytest.raises(TypeError):
  345. Record(value=None)
  346. def test_pset_field_explicit_non_optional():
  347. """
  348. If ``optional`` argument is ``False`` then ``pset_field`` is
  349. non-optional, i.e. does not allow ``None``.
  350. """
  351. class Record(PRecord):
  352. value = pset_field(int, optional=False)
  353. with pytest.raises(TypeError):
  354. Record(value=None)
  355. def test_pset_field_optional():
  356. """
  357. If ``optional`` argument is true, ``None`` is acceptable alternative
  358. to a set.
  359. """
  360. class Record(PRecord):
  361. value = pset_field(int, optional=True)
  362. assert ((Record(value=[1, 2]).value, Record(value=None).value) ==
  363. (pset([1, 2]), None))
  364. def test_pset_field_name():
  365. """
  366. The created set class name is based on the type of items in the set.
  367. """
  368. class Record(PRecord):
  369. value = pset_field(Something)
  370. value2 = pset_field(int)
  371. assert ((Record().value.__class__.__name__,
  372. Record().value2.__class__.__name__) ==
  373. ("SomethingPSet", "IntPSet"))
  374. def test_pset_multiple_types_field_name():
  375. """
  376. The created set class name is based on the multiple given types of
  377. items in the set.
  378. """
  379. class Record(PRecord):
  380. value = pset_field((Something, int))
  381. assert (Record().value.__class__.__name__ ==
  382. "SomethingIntPSet")
  383. def test_pset_field_name_string_type():
  384. """
  385. The created set class name is based on the type of items specified by name
  386. """
  387. class Record(PRecord):
  388. value = pset_field("record_test.Something")
  389. assert Record().value.__class__.__name__ == "SomethingPSet"
  390. def test_pset_multiple_string_types_field_name():
  391. """
  392. The created set class name is based on the multiple given types of
  393. items in the set specified by name
  394. """
  395. class Record(PRecord):
  396. value = pset_field(("record_test.Something", "record_test.Another"))
  397. assert Record().value.__class__.__name__ == "SomethingAnotherPSet"
  398. def test_pvector_field_initial_value():
  399. """
  400. ``pvector_field`` results in initial value that is empty.
  401. """
  402. class Record(PRecord):
  403. value = pvector_field(int)
  404. assert Record() == Record(value=[])
  405. def test_pvector_field_custom_initial():
  406. """
  407. A custom initial value can be passed in.
  408. """
  409. class Record(PRecord):
  410. value = pvector_field(int, initial=(1, 2))
  411. assert Record() == Record(value=[1, 2])
  412. def test_pvector_field_factory():
  413. """
  414. ``pvector_field`` has a factory that creates a ``PVector``.
  415. """
  416. class Record(PRecord):
  417. value = pvector_field(int)
  418. record = Record(value=[1, 2])
  419. assert isinstance(record.value, PVector)
  420. def test_pvector_field_checked_vector():
  421. """
  422. ``pvector_field`` results in a vector that enforces its type.
  423. """
  424. class Record(PRecord):
  425. value = pvector_field(int)
  426. record = Record(value=[1, 2])
  427. with pytest.raises(TypeError):
  428. record.value.append("hello") # type: ignore
  429. def test_pvector_field_checked_vector_multiple_types():
  430. """
  431. ``pvector_field`` results in a vector that enforces its types.
  432. """
  433. class Record(PRecord):
  434. value = pvector_field((int, str))
  435. record = Record(value=[1, 2, "hello"])
  436. with pytest.raises(TypeError):
  437. record.value.append(object())
  438. def test_pvector_field_type():
  439. """
  440. ``pvector_field`` enforces its type.
  441. """
  442. class Record(PRecord):
  443. value = pvector_field(int)
  444. record = Record()
  445. with pytest.raises(TypeError):
  446. record.set("value", None)
  447. def test_pvector_field_mandatory():
  448. """
  449. ``pvector_field`` is a mandatory field.
  450. """
  451. class Record(PRecord):
  452. value = pvector_field(int)
  453. record = Record(value=[1])
  454. with pytest.raises(InvariantException):
  455. record.remove("value")
  456. def test_pvector_field_default_non_optional():
  457. """
  458. By default ``pvector_field`` is non-optional, i.e. does not allow
  459. ``None``.
  460. """
  461. class Record(PRecord):
  462. value = pvector_field(int)
  463. with pytest.raises(TypeError):
  464. Record(value=None)
  465. def test_pvector_field_explicit_non_optional():
  466. """
  467. If ``optional`` argument is ``False`` then ``pvector_field`` is
  468. non-optional, i.e. does not allow ``None``.
  469. """
  470. class Record(PRecord):
  471. value = pvector_field(int, optional=False)
  472. with pytest.raises(TypeError):
  473. Record(value=None)
  474. def test_pvector_field_optional():
  475. """
  476. If ``optional`` argument is true, ``None`` is acceptable alternative
  477. to a sequence.
  478. """
  479. class Record(PRecord):
  480. value = pvector_field(int, optional=True)
  481. assert ((Record(value=[1, 2]).value, Record(value=None).value) ==
  482. (pvector([1, 2]), None))
  483. def test_pvector_field_name():
  484. """
  485. The created set class name is based on the type of items in the set.
  486. """
  487. class Record(PRecord):
  488. value = pvector_field(Something)
  489. value2 = pvector_field(int)
  490. assert ((Record().value.__class__.__name__,
  491. Record().value2.__class__.__name__) ==
  492. ("SomethingPVector", "IntPVector"))
  493. def test_pvector_multiple_types_field_name():
  494. """
  495. The created vector class name is based on the multiple given types of
  496. items in the vector.
  497. """
  498. class Record(PRecord):
  499. value = pvector_field((Something, int))
  500. assert (Record().value.__class__.__name__ ==
  501. "SomethingIntPVector")
  502. def test_pvector_field_name_string_type():
  503. """
  504. The created set class name is based on the type of items in the set
  505. specified by name.
  506. """
  507. class Record(PRecord):
  508. value = pvector_field("record_test.Something")
  509. assert Record().value.__class__.__name__ == "SomethingPVector"
  510. def test_pvector_multiple_string_types_field_name():
  511. """
  512. The created vector class name is based on the multiple given types of
  513. items in the vector.
  514. """
  515. class Record(PRecord):
  516. value = pvector_field(("record_test.Something", "record_test.Another"))
  517. assert Record().value.__class__.__name__ == "SomethingAnotherPVector"
  518. def test_pvector_field_create_from_nested_serialized_data():
  519. class Foo(PRecord):
  520. foo = field(type=str)
  521. class Bar(PRecord):
  522. bar = pvector_field(Foo)
  523. data = Bar(bar=v(Foo(foo="foo")))
  524. Bar.create(data.serialize()) == data
  525. def test_pmap_field_initial_value():
  526. """
  527. ``pmap_field`` results in initial value that is empty.
  528. """
  529. class Record(PRecord):
  530. value = pmap_field(int, int)
  531. assert Record() == Record(value={})
  532. def test_pmap_field_factory():
  533. """
  534. ``pmap_field`` has a factory that creates a ``PMap``.
  535. """
  536. class Record(PRecord):
  537. value = pmap_field(int, int)
  538. record = Record(value={1: 1234})
  539. assert isinstance(record.value, PMap)
  540. def test_pmap_field_checked_map_key():
  541. """
  542. ``pmap_field`` results in a map that enforces its key type.
  543. """
  544. class Record(PRecord):
  545. value = pmap_field(int, type(None))
  546. record = Record(value={1: None})
  547. with pytest.raises(TypeError):
  548. record.value.set("hello", None) # type: ignore
  549. def test_pmap_field_checked_map_value():
  550. """
  551. ``pmap_field`` results in a map that enforces its value type.
  552. """
  553. class Record(PRecord):
  554. value = pmap_field(int, type(None))
  555. record = Record(value={1: None})
  556. with pytest.raises(TypeError):
  557. record.value.set(2, 4) # type: ignore
  558. def test_pmap_field_checked_map_key_multiple_types():
  559. """
  560. ``pmap_field`` results in a map that enforces its key types.
  561. """
  562. class Record(PRecord):
  563. value = pmap_field((int, str), type(None))
  564. record = Record(value={1: None, "hello": None})
  565. with pytest.raises(TypeError):
  566. record.value.set(object(), None)
  567. def test_pmap_field_checked_map_value_multiple_types():
  568. """
  569. ``pmap_field`` results in a map that enforces its value types.
  570. """
  571. class Record(PRecord):
  572. value = pmap_field(int, (str, type(None)))
  573. record = Record(value={1: None, 3: "hello"})
  574. with pytest.raises(TypeError):
  575. record.value.set(2, 4)
  576. def test_pmap_field_mandatory():
  577. """
  578. ``pmap_field`` is a mandatory field.
  579. """
  580. class Record(PRecord):
  581. value = pmap_field(int, int)
  582. record = Record()
  583. with pytest.raises(InvariantException):
  584. record.remove("value")
  585. def test_pmap_field_default_non_optional():
  586. """
  587. By default ``pmap_field`` is non-optional, i.e. does not allow
  588. ``None``.
  589. """
  590. class Record(PRecord):
  591. value = pmap_field(int, int)
  592. # Ought to be TypeError, but pyrsistent doesn't quite allow that:
  593. with pytest.raises(AttributeError):
  594. Record(value=None)
  595. def test_pmap_field_explicit_non_optional():
  596. """
  597. If ``optional`` argument is ``False`` then ``pmap_field`` is
  598. non-optional, i.e. does not allow ``None``.
  599. """
  600. class Record(PRecord):
  601. value = pmap_field(int, int, optional=False)
  602. # Ought to be TypeError, but pyrsistent doesn't quite allow that:
  603. with pytest.raises(AttributeError):
  604. Record(value=None)
  605. def test_pmap_field_optional():
  606. """
  607. If ``optional`` argument is true, ``None`` is acceptable alternative
  608. to a set.
  609. """
  610. class Record(PRecord):
  611. value = pmap_field(int, int, optional=True)
  612. assert (Record(value={1: 2}).value, Record(value=None).value) == \
  613. (pmap({1: 2}), None)
  614. def test_pmap_field_name():
  615. """
  616. The created map class name is based on the types of items in the map.
  617. """
  618. class Record(PRecord):
  619. value = pmap_field(Something, Another)
  620. value2 = pmap_field(int, float)
  621. assert ((Record().value.__class__.__name__,
  622. Record().value2.__class__.__name__) ==
  623. ("SomethingToAnotherPMap", "IntToFloatPMap"))
  624. def test_pmap_field_name_multiple_types():
  625. """
  626. The created map class name is based on the types of items in the map,
  627. including when there are multiple supported types.
  628. """
  629. class Record(PRecord):
  630. value = pmap_field((Something, Another), int)
  631. value2 = pmap_field(str, (int, float))
  632. assert ((Record().value.__class__.__name__,
  633. Record().value2.__class__.__name__) ==
  634. ("SomethingAnotherToIntPMap", "StrToIntFloatPMap"))
  635. def test_pmap_field_name_string_type():
  636. """
  637. The created map class name is based on the types of items in the map
  638. specified by name.
  639. """
  640. class Record(PRecord):
  641. value = pmap_field("record_test.Something", "record_test.Another")
  642. assert Record().value.__class__.__name__ == "SomethingToAnotherPMap"
  643. def test_pmap_field_name_multiple_string_types():
  644. """
  645. The created map class name is based on the types of items in the map,
  646. including when there are multiple supported types.
  647. """
  648. class Record(PRecord):
  649. value = pmap_field(("record_test.Something", "record_test.Another"), int)
  650. value2 = pmap_field(str, ("record_test.Something", "record_test.Another"))
  651. assert ((Record().value.__class__.__name__,
  652. Record().value2.__class__.__name__) ==
  653. ("SomethingAnotherToIntPMap", "StrToSomethingAnotherPMap"))
  654. def test_pmap_field_invariant():
  655. """
  656. The ``invariant`` parameter is passed through to ``field``.
  657. """
  658. class Record(PRecord):
  659. value = pmap_field(
  660. int, int,
  661. invariant=(
  662. lambda pmap: (len(pmap) == 1, "Exactly one item required.")
  663. )
  664. )
  665. with pytest.raises(InvariantException):
  666. Record(value={})
  667. with pytest.raises(InvariantException):
  668. Record(value={1: 2, 3: 4})
  669. assert Record(value={1: 2}).value == {1: 2}
  670. def test_pmap_field_create_from_nested_serialized_data():
  671. class Foo(PRecord):
  672. foo = field(type=str)
  673. class Bar(PRecord):
  674. bar = pmap_field(str, Foo)
  675. data = Bar(bar=m(foo_key=Foo(foo="foo")))
  676. Bar.create(data.serialize()) == data
  677. def test_supports_weakref():
  678. import weakref
  679. weakref.ref(ARecord(x=1, y=2))
  680. def test_supports_lazy_initial_value_for_field():
  681. class MyRecord(PRecord):
  682. a = field(int, initial=lambda: 2)
  683. assert MyRecord() == MyRecord(a=2)
  684. def test_pickle_with_one_way_factory():
  685. """
  686. A field factory isn't called when restoring from pickle.
  687. """
  688. thing = UniqueThing(id='25544626-86da-4bce-b6b6-9186c0804d64')
  689. assert thing == pickle.loads(pickle.dumps(thing))