record_test.py 24 KB

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