| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292 |
- # Deliberately use "from dataclasses import *". Every name in __all__
- # is tested, so they all must be present. This is a way to catch
- # missing ones.
- from dataclasses import *
- import abc
- import pickle
- import inspect
- import builtins
- import types
- import weakref
- import unittest
- from unittest.mock import Mock
- from typing import ClassVar, Any, List, Union, Tuple, Dict, Generic, TypeVar, Optional, Protocol
- from typing import get_type_hints
- from collections import deque, OrderedDict, namedtuple
- from functools import total_ordering
- import typing # Needed for the string "typing.ClassVar[int]" to work as an annotation.
- import dataclasses # Needed for the string "dataclasses.InitVar[int]" to work as an annotation.
- # Just any custom exception we can catch.
- class CustomError(Exception): pass
- class TestCase(unittest.TestCase):
- def test_no_fields(self):
- @dataclass
- class C:
- pass
- o = C()
- self.assertEqual(len(fields(C)), 0)
- def test_no_fields_but_member_variable(self):
- @dataclass
- class C:
- i = 0
- o = C()
- self.assertEqual(len(fields(C)), 0)
- def test_one_field_no_default(self):
- @dataclass
- class C:
- x: int
- o = C(42)
- self.assertEqual(o.x, 42)
- def test_field_default_default_factory_error(self):
- msg = "cannot specify both default and default_factory"
- with self.assertRaisesRegex(ValueError, msg):
- @dataclass
- class C:
- x: int = field(default=1, default_factory=int)
- def test_field_repr(self):
- int_field = field(default=1, init=True, repr=False)
- int_field.name = "id"
- repr_output = repr(int_field)
- expected_output = "Field(name='id',type=None," \
- f"default=1,default_factory={MISSING!r}," \
- "init=True,repr=False,hash=None," \
- "compare=True,metadata=mappingproxy({})," \
- f"kw_only={MISSING!r}," \
- "_field_type=None)"
- self.assertEqual(repr_output, expected_output)
- def test_field_recursive_repr(self):
- rec_field = field()
- rec_field.type = rec_field
- rec_field.name = "id"
- repr_output = repr(rec_field)
- self.assertIn(",type=...,", repr_output)
- def test_recursive_annotation(self):
- class C:
- pass
- @dataclass
- class D:
- C: C = field()
- self.assertIn(",type=...,", repr(D.__dataclass_fields__["C"]))
- def test_named_init_params(self):
- @dataclass
- class C:
- x: int
- o = C(x=32)
- self.assertEqual(o.x, 32)
- def test_two_fields_one_default(self):
- @dataclass
- class C:
- x: int
- y: int = 0
- o = C(3)
- self.assertEqual((o.x, o.y), (3, 0))
- # Non-defaults following defaults.
- with self.assertRaisesRegex(TypeError,
- "non-default argument 'y' follows "
- "default argument"):
- @dataclass
- class C:
- x: int = 0
- y: int
- # A derived class adds a non-default field after a default one.
- with self.assertRaisesRegex(TypeError,
- "non-default argument 'y' follows "
- "default argument"):
- @dataclass
- class B:
- x: int = 0
- @dataclass
- class C(B):
- y: int
- # Override a base class field and add a default to
- # a field which didn't use to have a default.
- with self.assertRaisesRegex(TypeError,
- "non-default argument 'y' follows "
- "default argument"):
- @dataclass
- class B:
- x: int
- y: int
- @dataclass
- class C(B):
- x: int = 0
- def test_overwrite_hash(self):
- # Test that declaring this class isn't an error. It should
- # use the user-provided __hash__.
- @dataclass(frozen=True)
- class C:
- x: int
- def __hash__(self):
- return 301
- self.assertEqual(hash(C(100)), 301)
- # Test that declaring this class isn't an error. It should
- # use the generated __hash__.
- @dataclass(frozen=True)
- class C:
- x: int
- def __eq__(self, other):
- return False
- self.assertEqual(hash(C(100)), hash((100,)))
- # But this one should generate an exception, because with
- # unsafe_hash=True, it's an error to have a __hash__ defined.
- with self.assertRaisesRegex(TypeError,
- 'Cannot overwrite attribute __hash__'):
- @dataclass(unsafe_hash=True)
- class C:
- def __hash__(self):
- pass
- # Creating this class should not generate an exception,
- # because even though __hash__ exists before @dataclass is
- # called, (due to __eq__ being defined), since it's None
- # that's okay.
- @dataclass(unsafe_hash=True)
- class C:
- x: int
- def __eq__(self):
- pass
- # The generated hash function works as we'd expect.
- self.assertEqual(hash(C(10)), hash((10,)))
- # Creating this class should generate an exception, because
- # __hash__ exists and is not None, which it would be if it
- # had been auto-generated due to __eq__ being defined.
- with self.assertRaisesRegex(TypeError,
- 'Cannot overwrite attribute __hash__'):
- @dataclass(unsafe_hash=True)
- class C:
- x: int
- def __eq__(self):
- pass
- def __hash__(self):
- pass
- def test_overwrite_fields_in_derived_class(self):
- # Note that x from C1 replaces x in Base, but the order remains
- # the same as defined in Base.
- @dataclass
- class Base:
- x: Any = 15.0
- y: int = 0
- @dataclass
- class C1(Base):
- z: int = 10
- x: int = 15
- o = Base()
- self.assertEqual(repr(o), 'TestCase.test_overwrite_fields_in_derived_class.<locals>.Base(x=15.0, y=0)')
- o = C1()
- self.assertEqual(repr(o), 'TestCase.test_overwrite_fields_in_derived_class.<locals>.C1(x=15, y=0, z=10)')
- o = C1(x=5)
- self.assertEqual(repr(o), 'TestCase.test_overwrite_fields_in_derived_class.<locals>.C1(x=5, y=0, z=10)')
- def test_field_named_self(self):
- @dataclass
- class C:
- self: str
- c=C('foo')
- self.assertEqual(c.self, 'foo')
- # Make sure the first parameter is not named 'self'.
- sig = inspect.signature(C.__init__)
- first = next(iter(sig.parameters))
- self.assertNotEqual('self', first)
- # But we do use 'self' if no field named self.
- @dataclass
- class C:
- selfx: str
- # Make sure the first parameter is named 'self'.
- sig = inspect.signature(C.__init__)
- first = next(iter(sig.parameters))
- self.assertEqual('self', first)
- def test_field_named_object(self):
- @dataclass
- class C:
- object: str
- c = C('foo')
- self.assertEqual(c.object, 'foo')
- def test_field_named_object_frozen(self):
- @dataclass(frozen=True)
- class C:
- object: str
- c = C('foo')
- self.assertEqual(c.object, 'foo')
- def test_field_named_BUILTINS_frozen(self):
- # gh-96151
- @dataclass(frozen=True)
- class C:
- BUILTINS: int
- c = C(5)
- self.assertEqual(c.BUILTINS, 5)
- def test_field_named_like_builtin(self):
- # Attribute names can shadow built-in names
- # since code generation is used.
- # Ensure that this is not happening.
- exclusions = {'None', 'True', 'False'}
- builtins_names = sorted(
- b for b in builtins.__dict__.keys()
- if not b.startswith('__') and b not in exclusions
- )
- attributes = [(name, str) for name in builtins_names]
- C = make_dataclass('C', attributes)
- c = C(*[name for name in builtins_names])
- for name in builtins_names:
- self.assertEqual(getattr(c, name), name)
- def test_field_named_like_builtin_frozen(self):
- # Attribute names can shadow built-in names
- # since code generation is used.
- # Ensure that this is not happening
- # for frozen data classes.
- exclusions = {'None', 'True', 'False'}
- builtins_names = sorted(
- b for b in builtins.__dict__.keys()
- if not b.startswith('__') and b not in exclusions
- )
- attributes = [(name, str) for name in builtins_names]
- C = make_dataclass('C', attributes, frozen=True)
- c = C(*[name for name in builtins_names])
- for name in builtins_names:
- self.assertEqual(getattr(c, name), name)
- def test_0_field_compare(self):
- # Ensure that order=False is the default.
- @dataclass
- class C0:
- pass
- @dataclass(order=False)
- class C1:
- pass
- for cls in [C0, C1]:
- with self.subTest(cls=cls):
- self.assertEqual(cls(), cls())
- for idx, fn in enumerate([lambda a, b: a < b,
- lambda a, b: a <= b,
- lambda a, b: a > b,
- lambda a, b: a >= b]):
- with self.subTest(idx=idx):
- with self.assertRaisesRegex(TypeError,
- f"not supported between instances of '{cls.__name__}' and '{cls.__name__}'"):
- fn(cls(), cls())
- @dataclass(order=True)
- class C:
- pass
- self.assertLessEqual(C(), C())
- self.assertGreaterEqual(C(), C())
- def test_1_field_compare(self):
- # Ensure that order=False is the default.
- @dataclass
- class C0:
- x: int
- @dataclass(order=False)
- class C1:
- x: int
- for cls in [C0, C1]:
- with self.subTest(cls=cls):
- self.assertEqual(cls(1), cls(1))
- self.assertNotEqual(cls(0), cls(1))
- for idx, fn in enumerate([lambda a, b: a < b,
- lambda a, b: a <= b,
- lambda a, b: a > b,
- lambda a, b: a >= b]):
- with self.subTest(idx=idx):
- with self.assertRaisesRegex(TypeError,
- f"not supported between instances of '{cls.__name__}' and '{cls.__name__}'"):
- fn(cls(0), cls(0))
- @dataclass(order=True)
- class C:
- x: int
- self.assertLess(C(0), C(1))
- self.assertLessEqual(C(0), C(1))
- self.assertLessEqual(C(1), C(1))
- self.assertGreater(C(1), C(0))
- self.assertGreaterEqual(C(1), C(0))
- self.assertGreaterEqual(C(1), C(1))
- def test_simple_compare(self):
- # Ensure that order=False is the default.
- @dataclass
- class C0:
- x: int
- y: int
- @dataclass(order=False)
- class C1:
- x: int
- y: int
- for cls in [C0, C1]:
- with self.subTest(cls=cls):
- self.assertEqual(cls(0, 0), cls(0, 0))
- self.assertEqual(cls(1, 2), cls(1, 2))
- self.assertNotEqual(cls(1, 0), cls(0, 0))
- self.assertNotEqual(cls(1, 0), cls(1, 1))
- for idx, fn in enumerate([lambda a, b: a < b,
- lambda a, b: a <= b,
- lambda a, b: a > b,
- lambda a, b: a >= b]):
- with self.subTest(idx=idx):
- with self.assertRaisesRegex(TypeError,
- f"not supported between instances of '{cls.__name__}' and '{cls.__name__}'"):
- fn(cls(0, 0), cls(0, 0))
- @dataclass(order=True)
- class C:
- x: int
- y: int
- for idx, fn in enumerate([lambda a, b: a == b,
- lambda a, b: a <= b,
- lambda a, b: a >= b]):
- with self.subTest(idx=idx):
- self.assertTrue(fn(C(0, 0), C(0, 0)))
- for idx, fn in enumerate([lambda a, b: a < b,
- lambda a, b: a <= b,
- lambda a, b: a != b]):
- with self.subTest(idx=idx):
- self.assertTrue(fn(C(0, 0), C(0, 1)))
- self.assertTrue(fn(C(0, 1), C(1, 0)))
- self.assertTrue(fn(C(1, 0), C(1, 1)))
- for idx, fn in enumerate([lambda a, b: a > b,
- lambda a, b: a >= b,
- lambda a, b: a != b]):
- with self.subTest(idx=idx):
- self.assertTrue(fn(C(0, 1), C(0, 0)))
- self.assertTrue(fn(C(1, 0), C(0, 1)))
- self.assertTrue(fn(C(1, 1), C(1, 0)))
- def test_compare_subclasses(self):
- # Comparisons fail for subclasses, even if no fields
- # are added.
- @dataclass
- class B:
- i: int
- @dataclass
- class C(B):
- pass
- for idx, (fn, expected) in enumerate([(lambda a, b: a == b, False),
- (lambda a, b: a != b, True)]):
- with self.subTest(idx=idx):
- self.assertEqual(fn(B(0), C(0)), expected)
- for idx, fn in enumerate([lambda a, b: a < b,
- lambda a, b: a <= b,
- lambda a, b: a > b,
- lambda a, b: a >= b]):
- with self.subTest(idx=idx):
- with self.assertRaisesRegex(TypeError,
- "not supported between instances of 'B' and 'C'"):
- fn(B(0), C(0))
- def test_eq_order(self):
- # Test combining eq and order.
- for (eq, order, result ) in [
- (False, False, 'neither'),
- (False, True, 'exception'),
- (True, False, 'eq_only'),
- (True, True, 'both'),
- ]:
- with self.subTest(eq=eq, order=order):
- if result == 'exception':
- with self.assertRaisesRegex(ValueError, 'eq must be true if order is true'):
- @dataclass(eq=eq, order=order)
- class C:
- pass
- else:
- @dataclass(eq=eq, order=order)
- class C:
- pass
- if result == 'neither':
- self.assertNotIn('__eq__', C.__dict__)
- self.assertNotIn('__lt__', C.__dict__)
- self.assertNotIn('__le__', C.__dict__)
- self.assertNotIn('__gt__', C.__dict__)
- self.assertNotIn('__ge__', C.__dict__)
- elif result == 'both':
- self.assertIn('__eq__', C.__dict__)
- self.assertIn('__lt__', C.__dict__)
- self.assertIn('__le__', C.__dict__)
- self.assertIn('__gt__', C.__dict__)
- self.assertIn('__ge__', C.__dict__)
- elif result == 'eq_only':
- self.assertIn('__eq__', C.__dict__)
- self.assertNotIn('__lt__', C.__dict__)
- self.assertNotIn('__le__', C.__dict__)
- self.assertNotIn('__gt__', C.__dict__)
- self.assertNotIn('__ge__', C.__dict__)
- else:
- assert False, f'unknown result {result!r}'
- def test_field_no_default(self):
- @dataclass
- class C:
- x: int = field()
- self.assertEqual(C(5).x, 5)
- with self.assertRaisesRegex(TypeError,
- r"__init__\(\) missing 1 required "
- "positional argument: 'x'"):
- C()
- def test_field_default(self):
- default = object()
- @dataclass
- class C:
- x: object = field(default=default)
- self.assertIs(C.x, default)
- c = C(10)
- self.assertEqual(c.x, 10)
- # If we delete the instance attribute, we should then see the
- # class attribute.
- del c.x
- self.assertIs(c.x, default)
- self.assertIs(C().x, default)
- def test_not_in_repr(self):
- @dataclass
- class C:
- x: int = field(repr=False)
- with self.assertRaises(TypeError):
- C()
- c = C(10)
- self.assertEqual(repr(c), 'TestCase.test_not_in_repr.<locals>.C()')
- @dataclass
- class C:
- x: int = field(repr=False)
- y: int
- c = C(10, 20)
- self.assertEqual(repr(c), 'TestCase.test_not_in_repr.<locals>.C(y=20)')
- def test_not_in_compare(self):
- @dataclass
- class C:
- x: int = 0
- y: int = field(compare=False, default=4)
- self.assertEqual(C(), C(0, 20))
- self.assertEqual(C(1, 10), C(1, 20))
- self.assertNotEqual(C(3), C(4, 10))
- self.assertNotEqual(C(3, 10), C(4, 10))
- def test_no_unhashable_default(self):
- # See bpo-44674.
- class Unhashable:
- __hash__ = None
- unhashable_re = 'mutable default .* for field a is not allowed'
- with self.assertRaisesRegex(ValueError, unhashable_re):
- @dataclass
- class A:
- a: dict = {}
- with self.assertRaisesRegex(ValueError, unhashable_re):
- @dataclass
- class A:
- a: Any = Unhashable()
- # Make sure that the machinery looking for hashability is using the
- # class's __hash__, not the instance's __hash__.
- with self.assertRaisesRegex(ValueError, unhashable_re):
- unhashable = Unhashable()
- # This shouldn't make the variable hashable.
- unhashable.__hash__ = lambda: 0
- @dataclass
- class A:
- a: Any = unhashable
- def test_hash_field_rules(self):
- # Test all 6 cases of:
- # hash=True/False/None
- # compare=True/False
- for (hash_, compare, result ) in [
- (True, False, 'field' ),
- (True, True, 'field' ),
- (False, False, 'absent'),
- (False, True, 'absent'),
- (None, False, 'absent'),
- (None, True, 'field' ),
- ]:
- with self.subTest(hash=hash_, compare=compare):
- @dataclass(unsafe_hash=True)
- class C:
- x: int = field(compare=compare, hash=hash_, default=5)
- if result == 'field':
- # __hash__ contains the field.
- self.assertEqual(hash(C(5)), hash((5,)))
- elif result == 'absent':
- # The field is not present in the hash.
- self.assertEqual(hash(C(5)), hash(()))
- else:
- assert False, f'unknown result {result!r}'
- def test_init_false_no_default(self):
- # If init=False and no default value, then the field won't be
- # present in the instance.
- @dataclass
- class C:
- x: int = field(init=False)
- self.assertNotIn('x', C().__dict__)
- @dataclass
- class C:
- x: int
- y: int = 0
- z: int = field(init=False)
- t: int = 10
- self.assertNotIn('z', C(0).__dict__)
- self.assertEqual(vars(C(5)), {'t': 10, 'x': 5, 'y': 0})
- def test_class_marker(self):
- @dataclass
- class C:
- x: int
- y: str = field(init=False, default=None)
- z: str = field(repr=False)
- the_fields = fields(C)
- # the_fields is a tuple of 3 items, each value
- # is in __annotations__.
- self.assertIsInstance(the_fields, tuple)
- for f in the_fields:
- self.assertIs(type(f), Field)
- self.assertIn(f.name, C.__annotations__)
- self.assertEqual(len(the_fields), 3)
- self.assertEqual(the_fields[0].name, 'x')
- self.assertEqual(the_fields[0].type, int)
- self.assertFalse(hasattr(C, 'x'))
- self.assertTrue (the_fields[0].init)
- self.assertTrue (the_fields[0].repr)
- self.assertEqual(the_fields[1].name, 'y')
- self.assertEqual(the_fields[1].type, str)
- self.assertIsNone(getattr(C, 'y'))
- self.assertFalse(the_fields[1].init)
- self.assertTrue (the_fields[1].repr)
- self.assertEqual(the_fields[2].name, 'z')
- self.assertEqual(the_fields[2].type, str)
- self.assertFalse(hasattr(C, 'z'))
- self.assertTrue (the_fields[2].init)
- self.assertFalse(the_fields[2].repr)
- def test_field_order(self):
- @dataclass
- class B:
- a: str = 'B:a'
- b: str = 'B:b'
- c: str = 'B:c'
- @dataclass
- class C(B):
- b: str = 'C:b'
- self.assertEqual([(f.name, f.default) for f in fields(C)],
- [('a', 'B:a'),
- ('b', 'C:b'),
- ('c', 'B:c')])
- @dataclass
- class D(B):
- c: str = 'D:c'
- self.assertEqual([(f.name, f.default) for f in fields(D)],
- [('a', 'B:a'),
- ('b', 'B:b'),
- ('c', 'D:c')])
- @dataclass
- class E(D):
- a: str = 'E:a'
- d: str = 'E:d'
- self.assertEqual([(f.name, f.default) for f in fields(E)],
- [('a', 'E:a'),
- ('b', 'B:b'),
- ('c', 'D:c'),
- ('d', 'E:d')])
- def test_class_attrs(self):
- # We only have a class attribute if a default value is
- # specified, either directly or via a field with a default.
- default = object()
- @dataclass
- class C:
- x: int
- y: int = field(repr=False)
- z: object = default
- t: int = field(default=100)
- self.assertFalse(hasattr(C, 'x'))
- self.assertFalse(hasattr(C, 'y'))
- self.assertIs (C.z, default)
- self.assertEqual(C.t, 100)
- def test_disallowed_mutable_defaults(self):
- # For the known types, don't allow mutable default values.
- for typ, empty, non_empty in [(list, [], [1]),
- (dict, {}, {0:1}),
- (set, set(), set([1])),
- ]:
- with self.subTest(typ=typ):
- # Can't use a zero-length value.
- with self.assertRaisesRegex(ValueError,
- f'mutable default {typ} for field '
- 'x is not allowed'):
- @dataclass
- class Point:
- x: typ = empty
- # Nor a non-zero-length value
- with self.assertRaisesRegex(ValueError,
- f'mutable default {typ} for field '
- 'y is not allowed'):
- @dataclass
- class Point:
- y: typ = non_empty
- # Check subtypes also fail.
- class Subclass(typ): pass
- with self.assertRaisesRegex(ValueError,
- f"mutable default .*Subclass'>"
- ' for field z is not allowed'
- ):
- @dataclass
- class Point:
- z: typ = Subclass()
- # Because this is a ClassVar, it can be mutable.
- @dataclass
- class C:
- z: ClassVar[typ] = typ()
- # Because this is a ClassVar, it can be mutable.
- @dataclass
- class C:
- x: ClassVar[typ] = Subclass()
- def test_deliberately_mutable_defaults(self):
- # If a mutable default isn't in the known list of
- # (list, dict, set), then it's okay.
- class Mutable:
- def __init__(self):
- self.l = []
- @dataclass
- class C:
- x: Mutable
- # These 2 instances will share this value of x.
- lst = Mutable()
- o1 = C(lst)
- o2 = C(lst)
- self.assertEqual(o1, o2)
- o1.x.l.extend([1, 2])
- self.assertEqual(o1, o2)
- self.assertEqual(o1.x.l, [1, 2])
- self.assertIs(o1.x, o2.x)
- def test_no_options(self):
- # Call with dataclass().
- @dataclass()
- class C:
- x: int
- self.assertEqual(C(42).x, 42)
- def test_not_tuple(self):
- # Make sure we can't be compared to a tuple.
- @dataclass
- class Point:
- x: int
- y: int
- self.assertNotEqual(Point(1, 2), (1, 2))
- # And that we can't compare to another unrelated dataclass.
- @dataclass
- class C:
- x: int
- y: int
- self.assertNotEqual(Point(1, 3), C(1, 3))
- def test_not_other_dataclass(self):
- # Test that some of the problems with namedtuple don't happen
- # here.
- @dataclass
- class Point3D:
- x: int
- y: int
- z: int
- @dataclass
- class Date:
- year: int
- month: int
- day: int
- self.assertNotEqual(Point3D(2017, 6, 3), Date(2017, 6, 3))
- self.assertNotEqual(Point3D(1, 2, 3), (1, 2, 3))
- # Make sure we can't unpack.
- with self.assertRaisesRegex(TypeError, 'unpack'):
- x, y, z = Point3D(4, 5, 6)
- # Make sure another class with the same field names isn't
- # equal.
- @dataclass
- class Point3Dv1:
- x: int = 0
- y: int = 0
- z: int = 0
- self.assertNotEqual(Point3D(0, 0, 0), Point3Dv1())
- def test_function_annotations(self):
- # Some dummy class and instance to use as a default.
- class F:
- pass
- f = F()
- def validate_class(cls):
- # First, check __annotations__, even though they're not
- # function annotations.
- self.assertEqual(cls.__annotations__['i'], int)
- self.assertEqual(cls.__annotations__['j'], str)
- self.assertEqual(cls.__annotations__['k'], F)
- self.assertEqual(cls.__annotations__['l'], float)
- self.assertEqual(cls.__annotations__['z'], complex)
- # Verify __init__.
- signature = inspect.signature(cls.__init__)
- # Check the return type, should be None.
- self.assertIs(signature.return_annotation, None)
- # Check each parameter.
- params = iter(signature.parameters.values())
- param = next(params)
- # This is testing an internal name, and probably shouldn't be tested.
- self.assertEqual(param.name, 'self')
- param = next(params)
- self.assertEqual(param.name, 'i')
- self.assertIs (param.annotation, int)
- self.assertEqual(param.default, inspect.Parameter.empty)
- self.assertEqual(param.kind, inspect.Parameter.POSITIONAL_OR_KEYWORD)
- param = next(params)
- self.assertEqual(param.name, 'j')
- self.assertIs (param.annotation, str)
- self.assertEqual(param.default, inspect.Parameter.empty)
- self.assertEqual(param.kind, inspect.Parameter.POSITIONAL_OR_KEYWORD)
- param = next(params)
- self.assertEqual(param.name, 'k')
- self.assertIs (param.annotation, F)
- # Don't test for the default, since it's set to MISSING.
- self.assertEqual(param.kind, inspect.Parameter.POSITIONAL_OR_KEYWORD)
- param = next(params)
- self.assertEqual(param.name, 'l')
- self.assertIs (param.annotation, float)
- # Don't test for the default, since it's set to MISSING.
- self.assertEqual(param.kind, inspect.Parameter.POSITIONAL_OR_KEYWORD)
- self.assertRaises(StopIteration, next, params)
- @dataclass
- class C:
- i: int
- j: str
- k: F = f
- l: float=field(default=None)
- z: complex=field(default=3+4j, init=False)
- validate_class(C)
- # Now repeat with __hash__.
- @dataclass(frozen=True, unsafe_hash=True)
- class C:
- i: int
- j: str
- k: F = f
- l: float=field(default=None)
- z: complex=field(default=3+4j, init=False)
- validate_class(C)
- def test_missing_default(self):
- # Test that MISSING works the same as a default not being
- # specified.
- @dataclass
- class C:
- x: int=field(default=MISSING)
- with self.assertRaisesRegex(TypeError,
- r'__init__\(\) missing 1 required '
- 'positional argument'):
- C()
- self.assertNotIn('x', C.__dict__)
- @dataclass
- class D:
- x: int
- with self.assertRaisesRegex(TypeError,
- r'__init__\(\) missing 1 required '
- 'positional argument'):
- D()
- self.assertNotIn('x', D.__dict__)
- def test_missing_default_factory(self):
- # Test that MISSING works the same as a default factory not
- # being specified (which is really the same as a default not
- # being specified, too).
- @dataclass
- class C:
- x: int=field(default_factory=MISSING)
- with self.assertRaisesRegex(TypeError,
- r'__init__\(\) missing 1 required '
- 'positional argument'):
- C()
- self.assertNotIn('x', C.__dict__)
- @dataclass
- class D:
- x: int=field(default=MISSING, default_factory=MISSING)
- with self.assertRaisesRegex(TypeError,
- r'__init__\(\) missing 1 required '
- 'positional argument'):
- D()
- self.assertNotIn('x', D.__dict__)
- def test_missing_repr(self):
- self.assertIn('MISSING_TYPE object', repr(MISSING))
- def test_dont_include_other_annotations(self):
- @dataclass
- class C:
- i: int
- def foo(self) -> int:
- return 4
- @property
- def bar(self) -> int:
- return 5
- self.assertEqual(list(C.__annotations__), ['i'])
- self.assertEqual(C(10).foo(), 4)
- self.assertEqual(C(10).bar, 5)
- self.assertEqual(C(10).i, 10)
- def test_post_init(self):
- # Just make sure it gets called
- @dataclass
- class C:
- def __post_init__(self):
- raise CustomError()
- with self.assertRaises(CustomError):
- C()
- @dataclass
- class C:
- i: int = 10
- def __post_init__(self):
- if self.i == 10:
- raise CustomError()
- with self.assertRaises(CustomError):
- C()
- # post-init gets called, but doesn't raise. This is just
- # checking that self is used correctly.
- C(5)
- # If there's not an __init__, then post-init won't get called.
- @dataclass(init=False)
- class C:
- def __post_init__(self):
- raise CustomError()
- # Creating the class won't raise
- C()
- @dataclass
- class C:
- x: int = 0
- def __post_init__(self):
- self.x *= 2
- self.assertEqual(C().x, 0)
- self.assertEqual(C(2).x, 4)
- # Make sure that if we're frozen, post-init can't set
- # attributes.
- @dataclass(frozen=True)
- class C:
- x: int = 0
- def __post_init__(self):
- self.x *= 2
- with self.assertRaises(FrozenInstanceError):
- C()
- def test_post_init_super(self):
- # Make sure super() post-init isn't called by default.
- class B:
- def __post_init__(self):
- raise CustomError()
- @dataclass
- class C(B):
- def __post_init__(self):
- self.x = 5
- self.assertEqual(C().x, 5)
- # Now call super(), and it will raise.
- @dataclass
- class C(B):
- def __post_init__(self):
- super().__post_init__()
- with self.assertRaises(CustomError):
- C()
- # Make sure post-init is called, even if not defined in our
- # class.
- @dataclass
- class C(B):
- pass
- with self.assertRaises(CustomError):
- C()
- def test_post_init_staticmethod(self):
- flag = False
- @dataclass
- class C:
- x: int
- y: int
- @staticmethod
- def __post_init__():
- nonlocal flag
- flag = True
- self.assertFalse(flag)
- c = C(3, 4)
- self.assertEqual((c.x, c.y), (3, 4))
- self.assertTrue(flag)
- def test_post_init_classmethod(self):
- @dataclass
- class C:
- flag = False
- x: int
- y: int
- @classmethod
- def __post_init__(cls):
- cls.flag = True
- self.assertFalse(C.flag)
- c = C(3, 4)
- self.assertEqual((c.x, c.y), (3, 4))
- self.assertTrue(C.flag)
- def test_post_init_not_auto_added(self):
- # See bpo-46757, which had proposed always adding __post_init__. As
- # Raymond Hettinger pointed out, that would be a breaking change. So,
- # add a test to make sure that the current behavior doesn't change.
- @dataclass
- class A0:
- pass
- @dataclass
- class B0:
- b_called: bool = False
- def __post_init__(self):
- self.b_called = True
- @dataclass
- class C0(A0, B0):
- c_called: bool = False
- def __post_init__(self):
- super().__post_init__()
- self.c_called = True
- # Since A0 has no __post_init__, and one wasn't automatically added
- # (because that's the rule: it's never added by @dataclass, it's only
- # the class author that can add it), then B0.__post_init__ is called.
- # Verify that.
- c = C0()
- self.assertTrue(c.b_called)
- self.assertTrue(c.c_called)
- ######################################
- # Now, the same thing, except A1 defines __post_init__.
- @dataclass
- class A1:
- def __post_init__(self):
- pass
- @dataclass
- class B1:
- b_called: bool = False
- def __post_init__(self):
- self.b_called = True
- @dataclass
- class C1(A1, B1):
- c_called: bool = False
- def __post_init__(self):
- super().__post_init__()
- self.c_called = True
- # This time, B1.__post_init__ isn't being called. This mimics what
- # would happen if A1.__post_init__ had been automatically added,
- # instead of manually added as we see here. This test isn't really
- # needed, but I'm including it just to demonstrate the changed
- # behavior when A1 does define __post_init__.
- c = C1()
- self.assertFalse(c.b_called)
- self.assertTrue(c.c_called)
- def test_class_var(self):
- # Make sure ClassVars are ignored in __init__, __repr__, etc.
- @dataclass
- class C:
- x: int
- y: int = 10
- z: ClassVar[int] = 1000
- w: ClassVar[int] = 2000
- t: ClassVar[int] = 3000
- s: ClassVar = 4000
- c = C(5)
- self.assertEqual(repr(c), 'TestCase.test_class_var.<locals>.C(x=5, y=10)')
- self.assertEqual(len(fields(C)), 2) # We have 2 fields.
- self.assertEqual(len(C.__annotations__), 6) # And 4 ClassVars.
- self.assertEqual(c.z, 1000)
- self.assertEqual(c.w, 2000)
- self.assertEqual(c.t, 3000)
- self.assertEqual(c.s, 4000)
- C.z += 1
- self.assertEqual(c.z, 1001)
- c = C(20)
- self.assertEqual((c.x, c.y), (20, 10))
- self.assertEqual(c.z, 1001)
- self.assertEqual(c.w, 2000)
- self.assertEqual(c.t, 3000)
- self.assertEqual(c.s, 4000)
- def test_class_var_no_default(self):
- # If a ClassVar has no default value, it should not be set on the class.
- @dataclass
- class C:
- x: ClassVar[int]
- self.assertNotIn('x', C.__dict__)
- def test_class_var_default_factory(self):
- # It makes no sense for a ClassVar to have a default factory. When
- # would it be called? Call it yourself, since it's class-wide.
- with self.assertRaisesRegex(TypeError,
- 'cannot have a default factory'):
- @dataclass
- class C:
- x: ClassVar[int] = field(default_factory=int)
- self.assertNotIn('x', C.__dict__)
- def test_class_var_with_default(self):
- # If a ClassVar has a default value, it should be set on the class.
- @dataclass
- class C:
- x: ClassVar[int] = 10
- self.assertEqual(C.x, 10)
- @dataclass
- class C:
- x: ClassVar[int] = field(default=10)
- self.assertEqual(C.x, 10)
- def test_class_var_frozen(self):
- # Make sure ClassVars work even if we're frozen.
- @dataclass(frozen=True)
- class C:
- x: int
- y: int = 10
- z: ClassVar[int] = 1000
- w: ClassVar[int] = 2000
- t: ClassVar[int] = 3000
- c = C(5)
- self.assertEqual(repr(C(5)), 'TestCase.test_class_var_frozen.<locals>.C(x=5, y=10)')
- self.assertEqual(len(fields(C)), 2) # We have 2 fields
- self.assertEqual(len(C.__annotations__), 5) # And 3 ClassVars
- self.assertEqual(c.z, 1000)
- self.assertEqual(c.w, 2000)
- self.assertEqual(c.t, 3000)
- # We can still modify the ClassVar, it's only instances that are
- # frozen.
- C.z += 1
- self.assertEqual(c.z, 1001)
- c = C(20)
- self.assertEqual((c.x, c.y), (20, 10))
- self.assertEqual(c.z, 1001)
- self.assertEqual(c.w, 2000)
- self.assertEqual(c.t, 3000)
- def test_init_var_no_default(self):
- # If an InitVar has no default value, it should not be set on the class.
- @dataclass
- class C:
- x: InitVar[int]
- self.assertNotIn('x', C.__dict__)
- def test_init_var_default_factory(self):
- # It makes no sense for an InitVar to have a default factory. When
- # would it be called? Call it yourself, since it's class-wide.
- with self.assertRaisesRegex(TypeError,
- 'cannot have a default factory'):
- @dataclass
- class C:
- x: InitVar[int] = field(default_factory=int)
- self.assertNotIn('x', C.__dict__)
- def test_init_var_with_default(self):
- # If an InitVar has a default value, it should be set on the class.
- @dataclass
- class C:
- x: InitVar[int] = 10
- self.assertEqual(C.x, 10)
- @dataclass
- class C:
- x: InitVar[int] = field(default=10)
- self.assertEqual(C.x, 10)
- def test_init_var(self):
- @dataclass
- class C:
- x: int = None
- init_param: InitVar[int] = None
- def __post_init__(self, init_param):
- if self.x is None:
- self.x = init_param*2
- c = C(init_param=10)
- self.assertEqual(c.x, 20)
- def test_init_var_preserve_type(self):
- self.assertEqual(InitVar[int].type, int)
- # Make sure the repr is correct.
- self.assertEqual(repr(InitVar[int]), 'dataclasses.InitVar[int]')
- self.assertEqual(repr(InitVar[List[int]]),
- 'dataclasses.InitVar[typing.List[int]]')
- self.assertEqual(repr(InitVar[list[int]]),
- 'dataclasses.InitVar[list[int]]')
- self.assertEqual(repr(InitVar[int|str]),
- 'dataclasses.InitVar[int | str]')
- def test_init_var_inheritance(self):
- # Note that this deliberately tests that a dataclass need not
- # have a __post_init__ function if it has an InitVar field.
- # It could just be used in a derived class, as shown here.
- @dataclass
- class Base:
- x: int
- init_base: InitVar[int]
- # We can instantiate by passing the InitVar, even though
- # it's not used.
- b = Base(0, 10)
- self.assertEqual(vars(b), {'x': 0})
- @dataclass
- class C(Base):
- y: int
- init_derived: InitVar[int]
- def __post_init__(self, init_base, init_derived):
- self.x = self.x + init_base
- self.y = self.y + init_derived
- c = C(10, 11, 50, 51)
- self.assertEqual(vars(c), {'x': 21, 'y': 101})
- def test_default_factory(self):
- # Test a factory that returns a new list.
- @dataclass
- class C:
- x: int
- y: list = field(default_factory=list)
- c0 = C(3)
- c1 = C(3)
- self.assertEqual(c0.x, 3)
- self.assertEqual(c0.y, [])
- self.assertEqual(c0, c1)
- self.assertIsNot(c0.y, c1.y)
- self.assertEqual(astuple(C(5, [1])), (5, [1]))
- # Test a factory that returns a shared list.
- l = []
- @dataclass
- class C:
- x: int
- y: list = field(default_factory=lambda: l)
- c0 = C(3)
- c1 = C(3)
- self.assertEqual(c0.x, 3)
- self.assertEqual(c0.y, [])
- self.assertEqual(c0, c1)
- self.assertIs(c0.y, c1.y)
- self.assertEqual(astuple(C(5, [1])), (5, [1]))
- # Test various other field flags.
- # repr
- @dataclass
- class C:
- x: list = field(default_factory=list, repr=False)
- self.assertEqual(repr(C()), 'TestCase.test_default_factory.<locals>.C()')
- self.assertEqual(C().x, [])
- # hash
- @dataclass(unsafe_hash=True)
- class C:
- x: list = field(default_factory=list, hash=False)
- self.assertEqual(astuple(C()), ([],))
- self.assertEqual(hash(C()), hash(()))
- # init (see also test_default_factory_with_no_init)
- @dataclass
- class C:
- x: list = field(default_factory=list, init=False)
- self.assertEqual(astuple(C()), ([],))
- # compare
- @dataclass
- class C:
- x: list = field(default_factory=list, compare=False)
- self.assertEqual(C(), C([1]))
- def test_default_factory_with_no_init(self):
- # We need a factory with a side effect.
- factory = Mock()
- @dataclass
- class C:
- x: list = field(default_factory=factory, init=False)
- # Make sure the default factory is called for each new instance.
- C().x
- self.assertEqual(factory.call_count, 1)
- C().x
- self.assertEqual(factory.call_count, 2)
- def test_default_factory_not_called_if_value_given(self):
- # We need a factory that we can test if it's been called.
- factory = Mock()
- @dataclass
- class C:
- x: int = field(default_factory=factory)
- # Make sure that if a field has a default factory function,
- # it's not called if a value is specified.
- C().x
- self.assertEqual(factory.call_count, 1)
- self.assertEqual(C(10).x, 10)
- self.assertEqual(factory.call_count, 1)
- C().x
- self.assertEqual(factory.call_count, 2)
- def test_default_factory_derived(self):
- # See bpo-32896.
- @dataclass
- class Foo:
- x: dict = field(default_factory=dict)
- @dataclass
- class Bar(Foo):
- y: int = 1
- self.assertEqual(Foo().x, {})
- self.assertEqual(Bar().x, {})
- self.assertEqual(Bar().y, 1)
- @dataclass
- class Baz(Foo):
- pass
- self.assertEqual(Baz().x, {})
- def test_intermediate_non_dataclass(self):
- # Test that an intermediate class that defines
- # annotations does not define fields.
- @dataclass
- class A:
- x: int
- class B(A):
- y: int
- @dataclass
- class C(B):
- z: int
- c = C(1, 3)
- self.assertEqual((c.x, c.z), (1, 3))
- # .y was not initialized.
- with self.assertRaisesRegex(AttributeError,
- 'object has no attribute'):
- c.y
- # And if we again derive a non-dataclass, no fields are added.
- class D(C):
- t: int
- d = D(4, 5)
- self.assertEqual((d.x, d.z), (4, 5))
- def test_classvar_default_factory(self):
- # It's an error for a ClassVar to have a factory function.
- with self.assertRaisesRegex(TypeError,
- 'cannot have a default factory'):
- @dataclass
- class C:
- x: ClassVar[int] = field(default_factory=int)
- def test_is_dataclass(self):
- class NotDataClass:
- pass
- self.assertFalse(is_dataclass(0))
- self.assertFalse(is_dataclass(int))
- self.assertFalse(is_dataclass(NotDataClass))
- self.assertFalse(is_dataclass(NotDataClass()))
- @dataclass
- class C:
- x: int
- @dataclass
- class D:
- d: C
- e: int
- c = C(10)
- d = D(c, 4)
- self.assertTrue(is_dataclass(C))
- self.assertTrue(is_dataclass(c))
- self.assertFalse(is_dataclass(c.x))
- self.assertTrue(is_dataclass(d.d))
- self.assertFalse(is_dataclass(d.e))
- def test_is_dataclass_when_getattr_always_returns(self):
- # See bpo-37868.
- class A:
- def __getattr__(self, key):
- return 0
- self.assertFalse(is_dataclass(A))
- a = A()
- # Also test for an instance attribute.
- class B:
- pass
- b = B()
- b.__dataclass_fields__ = []
- for obj in a, b:
- with self.subTest(obj=obj):
- self.assertFalse(is_dataclass(obj))
- # Indirect tests for _is_dataclass_instance().
- with self.assertRaisesRegex(TypeError, 'should be called on dataclass instances'):
- asdict(obj)
- with self.assertRaisesRegex(TypeError, 'should be called on dataclass instances'):
- astuple(obj)
- with self.assertRaisesRegex(TypeError, 'should be called on dataclass instances'):
- replace(obj, x=0)
- def test_is_dataclass_genericalias(self):
- @dataclass
- class A(types.GenericAlias):
- origin: type
- args: type
- self.assertTrue(is_dataclass(A))
- a = A(list, int)
- self.assertTrue(is_dataclass(type(a)))
- self.assertTrue(is_dataclass(a))
- def test_helper_fields_with_class_instance(self):
- # Check that we can call fields() on either a class or instance,
- # and get back the same thing.
- @dataclass
- class C:
- x: int
- y: float
- self.assertEqual(fields(C), fields(C(0, 0.0)))
- def test_helper_fields_exception(self):
- # Check that TypeError is raised if not passed a dataclass or
- # instance.
- with self.assertRaisesRegex(TypeError, 'dataclass type or instance'):
- fields(0)
- class C: pass
- with self.assertRaisesRegex(TypeError, 'dataclass type or instance'):
- fields(C)
- with self.assertRaisesRegex(TypeError, 'dataclass type or instance'):
- fields(C())
- def test_helper_asdict(self):
- # Basic tests for asdict(), it should return a new dictionary.
- @dataclass
- class C:
- x: int
- y: int
- c = C(1, 2)
- self.assertEqual(asdict(c), {'x': 1, 'y': 2})
- self.assertEqual(asdict(c), asdict(c))
- self.assertIsNot(asdict(c), asdict(c))
- c.x = 42
- self.assertEqual(asdict(c), {'x': 42, 'y': 2})
- self.assertIs(type(asdict(c)), dict)
- def test_helper_asdict_raises_on_classes(self):
- # asdict() should raise on a class object.
- @dataclass
- class C:
- x: int
- y: int
- with self.assertRaisesRegex(TypeError, 'dataclass instance'):
- asdict(C)
- with self.assertRaisesRegex(TypeError, 'dataclass instance'):
- asdict(int)
- def test_helper_asdict_copy_values(self):
- @dataclass
- class C:
- x: int
- y: List[int] = field(default_factory=list)
- initial = []
- c = C(1, initial)
- d = asdict(c)
- self.assertEqual(d['y'], initial)
- self.assertIsNot(d['y'], initial)
- c = C(1)
- d = asdict(c)
- d['y'].append(1)
- self.assertEqual(c.y, [])
- def test_helper_asdict_nested(self):
- @dataclass
- class UserId:
- token: int
- group: int
- @dataclass
- class User:
- name: str
- id: UserId
- u = User('Joe', UserId(123, 1))
- d = asdict(u)
- self.assertEqual(d, {'name': 'Joe', 'id': {'token': 123, 'group': 1}})
- self.assertIsNot(asdict(u), asdict(u))
- u.id.group = 2
- self.assertEqual(asdict(u), {'name': 'Joe',
- 'id': {'token': 123, 'group': 2}})
- def test_helper_asdict_builtin_containers(self):
- @dataclass
- class User:
- name: str
- id: int
- @dataclass
- class GroupList:
- id: int
- users: List[User]
- @dataclass
- class GroupTuple:
- id: int
- users: Tuple[User, ...]
- @dataclass
- class GroupDict:
- id: int
- users: Dict[str, User]
- a = User('Alice', 1)
- b = User('Bob', 2)
- gl = GroupList(0, [a, b])
- gt = GroupTuple(0, (a, b))
- gd = GroupDict(0, {'first': a, 'second': b})
- self.assertEqual(asdict(gl), {'id': 0, 'users': [{'name': 'Alice', 'id': 1},
- {'name': 'Bob', 'id': 2}]})
- self.assertEqual(asdict(gt), {'id': 0, 'users': ({'name': 'Alice', 'id': 1},
- {'name': 'Bob', 'id': 2})})
- self.assertEqual(asdict(gd), {'id': 0, 'users': {'first': {'name': 'Alice', 'id': 1},
- 'second': {'name': 'Bob', 'id': 2}}})
- def test_helper_asdict_builtin_object_containers(self):
- @dataclass
- class Child:
- d: object
- @dataclass
- class Parent:
- child: Child
- self.assertEqual(asdict(Parent(Child([1]))), {'child': {'d': [1]}})
- self.assertEqual(asdict(Parent(Child({1: 2}))), {'child': {'d': {1: 2}}})
- def test_helper_asdict_factory(self):
- @dataclass
- class C:
- x: int
- y: int
- c = C(1, 2)
- d = asdict(c, dict_factory=OrderedDict)
- self.assertEqual(d, OrderedDict([('x', 1), ('y', 2)]))
- self.assertIsNot(d, asdict(c, dict_factory=OrderedDict))
- c.x = 42
- d = asdict(c, dict_factory=OrderedDict)
- self.assertEqual(d, OrderedDict([('x', 42), ('y', 2)]))
- self.assertIs(type(d), OrderedDict)
- def test_helper_asdict_namedtuple(self):
- T = namedtuple('T', 'a b c')
- @dataclass
- class C:
- x: str
- y: T
- c = C('outer', T(1, C('inner', T(11, 12, 13)), 2))
- d = asdict(c)
- self.assertEqual(d, {'x': 'outer',
- 'y': T(1,
- {'x': 'inner',
- 'y': T(11, 12, 13)},
- 2),
- }
- )
- # Now with a dict_factory. OrderedDict is convenient, but
- # since it compares to dicts, we also need to have separate
- # assertIs tests.
- d = asdict(c, dict_factory=OrderedDict)
- self.assertEqual(d, {'x': 'outer',
- 'y': T(1,
- {'x': 'inner',
- 'y': T(11, 12, 13)},
- 2),
- }
- )
- # Make sure that the returned dicts are actually OrderedDicts.
- self.assertIs(type(d), OrderedDict)
- self.assertIs(type(d['y'][1]), OrderedDict)
- def test_helper_asdict_namedtuple_key(self):
- # Ensure that a field that contains a dict which has a
- # namedtuple as a key works with asdict().
- @dataclass
- class C:
- f: dict
- T = namedtuple('T', 'a')
- c = C({T('an a'): 0})
- self.assertEqual(asdict(c), {'f': {T(a='an a'): 0}})
- def test_helper_asdict_namedtuple_derived(self):
- class T(namedtuple('Tbase', 'a')):
- def my_a(self):
- return self.a
- @dataclass
- class C:
- f: T
- t = T(6)
- c = C(t)
- d = asdict(c)
- self.assertEqual(d, {'f': T(a=6)})
- # Make sure that t has been copied, not used directly.
- self.assertIsNot(d['f'], t)
- self.assertEqual(d['f'].my_a(), 6)
- def test_helper_astuple(self):
- # Basic tests for astuple(), it should return a new tuple.
- @dataclass
- class C:
- x: int
- y: int = 0
- c = C(1)
- self.assertEqual(astuple(c), (1, 0))
- self.assertEqual(astuple(c), astuple(c))
- self.assertIsNot(astuple(c), astuple(c))
- c.y = 42
- self.assertEqual(astuple(c), (1, 42))
- self.assertIs(type(astuple(c)), tuple)
- def test_helper_astuple_raises_on_classes(self):
- # astuple() should raise on a class object.
- @dataclass
- class C:
- x: int
- y: int
- with self.assertRaisesRegex(TypeError, 'dataclass instance'):
- astuple(C)
- with self.assertRaisesRegex(TypeError, 'dataclass instance'):
- astuple(int)
- def test_helper_astuple_copy_values(self):
- @dataclass
- class C:
- x: int
- y: List[int] = field(default_factory=list)
- initial = []
- c = C(1, initial)
- t = astuple(c)
- self.assertEqual(t[1], initial)
- self.assertIsNot(t[1], initial)
- c = C(1)
- t = astuple(c)
- t[1].append(1)
- self.assertEqual(c.y, [])
- def test_helper_astuple_nested(self):
- @dataclass
- class UserId:
- token: int
- group: int
- @dataclass
- class User:
- name: str
- id: UserId
- u = User('Joe', UserId(123, 1))
- t = astuple(u)
- self.assertEqual(t, ('Joe', (123, 1)))
- self.assertIsNot(astuple(u), astuple(u))
- u.id.group = 2
- self.assertEqual(astuple(u), ('Joe', (123, 2)))
- def test_helper_astuple_builtin_containers(self):
- @dataclass
- class User:
- name: str
- id: int
- @dataclass
- class GroupList:
- id: int
- users: List[User]
- @dataclass
- class GroupTuple:
- id: int
- users: Tuple[User, ...]
- @dataclass
- class GroupDict:
- id: int
- users: Dict[str, User]
- a = User('Alice', 1)
- b = User('Bob', 2)
- gl = GroupList(0, [a, b])
- gt = GroupTuple(0, (a, b))
- gd = GroupDict(0, {'first': a, 'second': b})
- self.assertEqual(astuple(gl), (0, [('Alice', 1), ('Bob', 2)]))
- self.assertEqual(astuple(gt), (0, (('Alice', 1), ('Bob', 2))))
- self.assertEqual(astuple(gd), (0, {'first': ('Alice', 1), 'second': ('Bob', 2)}))
- def test_helper_astuple_builtin_object_containers(self):
- @dataclass
- class Child:
- d: object
- @dataclass
- class Parent:
- child: Child
- self.assertEqual(astuple(Parent(Child([1]))), (([1],),))
- self.assertEqual(astuple(Parent(Child({1: 2}))), (({1: 2},),))
- def test_helper_astuple_factory(self):
- @dataclass
- class C:
- x: int
- y: int
- NT = namedtuple('NT', 'x y')
- def nt(lst):
- return NT(*lst)
- c = C(1, 2)
- t = astuple(c, tuple_factory=nt)
- self.assertEqual(t, NT(1, 2))
- self.assertIsNot(t, astuple(c, tuple_factory=nt))
- c.x = 42
- t = astuple(c, tuple_factory=nt)
- self.assertEqual(t, NT(42, 2))
- self.assertIs(type(t), NT)
- def test_helper_astuple_namedtuple(self):
- T = namedtuple('T', 'a b c')
- @dataclass
- class C:
- x: str
- y: T
- c = C('outer', T(1, C('inner', T(11, 12, 13)), 2))
- t = astuple(c)
- self.assertEqual(t, ('outer', T(1, ('inner', (11, 12, 13)), 2)))
- # Now, using a tuple_factory. list is convenient here.
- t = astuple(c, tuple_factory=list)
- self.assertEqual(t, ['outer', T(1, ['inner', T(11, 12, 13)], 2)])
- def test_dynamic_class_creation(self):
- cls_dict = {'__annotations__': {'x': int, 'y': int},
- }
- # Create the class.
- cls = type('C', (), cls_dict)
- # Make it a dataclass.
- cls1 = dataclass(cls)
- self.assertEqual(cls1, cls)
- self.assertEqual(asdict(cls(1, 2)), {'x': 1, 'y': 2})
- def test_dynamic_class_creation_using_field(self):
- cls_dict = {'__annotations__': {'x': int, 'y': int},
- 'y': field(default=5),
- }
- # Create the class.
- cls = type('C', (), cls_dict)
- # Make it a dataclass.
- cls1 = dataclass(cls)
- self.assertEqual(cls1, cls)
- self.assertEqual(asdict(cls1(1)), {'x': 1, 'y': 5})
- def test_init_in_order(self):
- @dataclass
- class C:
- a: int
- b: int = field()
- c: list = field(default_factory=list, init=False)
- d: list = field(default_factory=list)
- e: int = field(default=4, init=False)
- f: int = 4
- calls = []
- def setattr(self, name, value):
- calls.append((name, value))
- C.__setattr__ = setattr
- c = C(0, 1)
- self.assertEqual(('a', 0), calls[0])
- self.assertEqual(('b', 1), calls[1])
- self.assertEqual(('c', []), calls[2])
- self.assertEqual(('d', []), calls[3])
- self.assertNotIn(('e', 4), calls)
- self.assertEqual(('f', 4), calls[4])
- def test_items_in_dicts(self):
- @dataclass
- class C:
- a: int
- b: list = field(default_factory=list, init=False)
- c: list = field(default_factory=list)
- d: int = field(default=4, init=False)
- e: int = 0
- c = C(0)
- # Class dict
- self.assertNotIn('a', C.__dict__)
- self.assertNotIn('b', C.__dict__)
- self.assertNotIn('c', C.__dict__)
- self.assertIn('d', C.__dict__)
- self.assertEqual(C.d, 4)
- self.assertIn('e', C.__dict__)
- self.assertEqual(C.e, 0)
- # Instance dict
- self.assertIn('a', c.__dict__)
- self.assertEqual(c.a, 0)
- self.assertIn('b', c.__dict__)
- self.assertEqual(c.b, [])
- self.assertIn('c', c.__dict__)
- self.assertEqual(c.c, [])
- self.assertNotIn('d', c.__dict__)
- self.assertIn('e', c.__dict__)
- self.assertEqual(c.e, 0)
- def test_alternate_classmethod_constructor(self):
- # Since __post_init__ can't take params, use a classmethod
- # alternate constructor. This is mostly an example to show
- # how to use this technique.
- @dataclass
- class C:
- x: int
- @classmethod
- def from_file(cls, filename):
- # In a real example, create a new instance
- # and populate 'x' from contents of a file.
- value_in_file = 20
- return cls(value_in_file)
- self.assertEqual(C.from_file('filename').x, 20)
- def test_field_metadata_default(self):
- # Make sure the default metadata is read-only and of
- # zero length.
- @dataclass
- class C:
- i: int
- self.assertFalse(fields(C)[0].metadata)
- self.assertEqual(len(fields(C)[0].metadata), 0)
- with self.assertRaisesRegex(TypeError,
- 'does not support item assignment'):
- fields(C)[0].metadata['test'] = 3
- def test_field_metadata_mapping(self):
- # Make sure only a mapping can be passed as metadata
- # zero length.
- with self.assertRaises(TypeError):
- @dataclass
- class C:
- i: int = field(metadata=0)
- # Make sure an empty dict works.
- d = {}
- @dataclass
- class C:
- i: int = field(metadata=d)
- self.assertFalse(fields(C)[0].metadata)
- self.assertEqual(len(fields(C)[0].metadata), 0)
- # Update should work (see bpo-35960).
- d['foo'] = 1
- self.assertEqual(len(fields(C)[0].metadata), 1)
- self.assertEqual(fields(C)[0].metadata['foo'], 1)
- with self.assertRaisesRegex(TypeError,
- 'does not support item assignment'):
- fields(C)[0].metadata['test'] = 3
- # Make sure a non-empty dict works.
- d = {'test': 10, 'bar': '42', 3: 'three'}
- @dataclass
- class C:
- i: int = field(metadata=d)
- self.assertEqual(len(fields(C)[0].metadata), 3)
- self.assertEqual(fields(C)[0].metadata['test'], 10)
- self.assertEqual(fields(C)[0].metadata['bar'], '42')
- self.assertEqual(fields(C)[0].metadata[3], 'three')
- # Update should work.
- d['foo'] = 1
- self.assertEqual(len(fields(C)[0].metadata), 4)
- self.assertEqual(fields(C)[0].metadata['foo'], 1)
- with self.assertRaises(KeyError):
- # Non-existent key.
- fields(C)[0].metadata['baz']
- with self.assertRaisesRegex(TypeError,
- 'does not support item assignment'):
- fields(C)[0].metadata['test'] = 3
- def test_field_metadata_custom_mapping(self):
- # Try a custom mapping.
- class SimpleNameSpace:
- def __init__(self, **kw):
- self.__dict__.update(kw)
- def __getitem__(self, item):
- if item == 'xyzzy':
- return 'plugh'
- return getattr(self, item)
- def __len__(self):
- return self.__dict__.__len__()
- @dataclass
- class C:
- i: int = field(metadata=SimpleNameSpace(a=10))
- self.assertEqual(len(fields(C)[0].metadata), 1)
- self.assertEqual(fields(C)[0].metadata['a'], 10)
- with self.assertRaises(AttributeError):
- fields(C)[0].metadata['b']
- # Make sure we're still talking to our custom mapping.
- self.assertEqual(fields(C)[0].metadata['xyzzy'], 'plugh')
- def test_generic_dataclasses(self):
- T = TypeVar('T')
- @dataclass
- class LabeledBox(Generic[T]):
- content: T
- label: str = '<unknown>'
- box = LabeledBox(42)
- self.assertEqual(box.content, 42)
- self.assertEqual(box.label, '<unknown>')
- # Subscripting the resulting class should work, etc.
- Alias = List[LabeledBox[int]]
- def test_generic_extending(self):
- S = TypeVar('S')
- T = TypeVar('T')
- @dataclass
- class Base(Generic[T, S]):
- x: T
- y: S
- @dataclass
- class DataDerived(Base[int, T]):
- new_field: str
- Alias = DataDerived[str]
- c = Alias(0, 'test1', 'test2')
- self.assertEqual(astuple(c), (0, 'test1', 'test2'))
- class NonDataDerived(Base[int, T]):
- def new_method(self):
- return self.y
- Alias = NonDataDerived[float]
- c = Alias(10, 1.0)
- self.assertEqual(c.new_method(), 1.0)
- def test_generic_dynamic(self):
- T = TypeVar('T')
- @dataclass
- class Parent(Generic[T]):
- x: T
- Child = make_dataclass('Child', [('y', T), ('z', Optional[T], None)],
- bases=(Parent[int], Generic[T]), namespace={'other': 42})
- self.assertIs(Child[int](1, 2).z, None)
- self.assertEqual(Child[int](1, 2, 3).z, 3)
- self.assertEqual(Child[int](1, 2, 3).other, 42)
- # Check that type aliases work correctly.
- Alias = Child[T]
- self.assertEqual(Alias[int](1, 2).x, 1)
- # Check MRO resolution.
- self.assertEqual(Child.__mro__, (Child, Parent, Generic, object))
- def test_dataclasses_pickleable(self):
- global P, Q, R
- @dataclass
- class P:
- x: int
- y: int = 0
- @dataclass
- class Q:
- x: int
- y: int = field(default=0, init=False)
- @dataclass
- class R:
- x: int
- y: List[int] = field(default_factory=list)
- q = Q(1)
- q.y = 2
- samples = [P(1), P(1, 2), Q(1), q, R(1), R(1, [2, 3, 4])]
- for sample in samples:
- for proto in range(pickle.HIGHEST_PROTOCOL + 1):
- with self.subTest(sample=sample, proto=proto):
- new_sample = pickle.loads(pickle.dumps(sample, proto))
- self.assertEqual(sample.x, new_sample.x)
- self.assertEqual(sample.y, new_sample.y)
- self.assertIsNot(sample, new_sample)
- new_sample.x = 42
- another_new_sample = pickle.loads(pickle.dumps(new_sample, proto))
- self.assertEqual(new_sample.x, another_new_sample.x)
- self.assertEqual(sample.y, another_new_sample.y)
- def test_dataclasses_qualnames(self):
- @dataclass(order=True, unsafe_hash=True, frozen=True)
- class A:
- x: int
- y: int
- self.assertEqual(A.__init__.__name__, "__init__")
- for function in (
- '__eq__',
- '__lt__',
- '__le__',
- '__gt__',
- '__ge__',
- '__hash__',
- '__init__',
- '__repr__',
- '__setattr__',
- '__delattr__',
- ):
- self.assertEqual(getattr(A, function).__qualname__, f"TestCase.test_dataclasses_qualnames.<locals>.A.{function}")
- with self.assertRaisesRegex(TypeError, r"A\.__init__\(\) missing"):
- A()
- class TestFieldNoAnnotation(unittest.TestCase):
- def test_field_without_annotation(self):
- with self.assertRaisesRegex(TypeError,
- "'f' is a field but has no type annotation"):
- @dataclass
- class C:
- f = field()
- def test_field_without_annotation_but_annotation_in_base(self):
- @dataclass
- class B:
- f: int
- with self.assertRaisesRegex(TypeError,
- "'f' is a field but has no type annotation"):
- # This is still an error: make sure we don't pick up the
- # type annotation in the base class.
- @dataclass
- class C(B):
- f = field()
- def test_field_without_annotation_but_annotation_in_base_not_dataclass(self):
- # Same test, but with the base class not a dataclass.
- class B:
- f: int
- with self.assertRaisesRegex(TypeError,
- "'f' is a field but has no type annotation"):
- # This is still an error: make sure we don't pick up the
- # type annotation in the base class.
- @dataclass
- class C(B):
- f = field()
- class TestDocString(unittest.TestCase):
- def assertDocStrEqual(self, a, b):
- # Because 3.6 and 3.7 differ in how inspect.signature work
- # (see bpo #32108), for the time being just compare them with
- # whitespace stripped.
- self.assertEqual(a.replace(' ', ''), b.replace(' ', ''))
- def test_existing_docstring_not_overridden(self):
- @dataclass
- class C:
- """Lorem ipsum"""
- x: int
- self.assertEqual(C.__doc__, "Lorem ipsum")
- def test_docstring_no_fields(self):
- @dataclass
- class C:
- pass
- self.assertDocStrEqual(C.__doc__, "C()")
- def test_docstring_one_field(self):
- @dataclass
- class C:
- x: int
- self.assertDocStrEqual(C.__doc__, "C(x:int)")
- def test_docstring_two_fields(self):
- @dataclass
- class C:
- x: int
- y: int
- self.assertDocStrEqual(C.__doc__, "C(x:int, y:int)")
- def test_docstring_three_fields(self):
- @dataclass
- class C:
- x: int
- y: int
- z: str
- self.assertDocStrEqual(C.__doc__, "C(x:int, y:int, z:str)")
- def test_docstring_one_field_with_default(self):
- @dataclass
- class C:
- x: int = 3
- self.assertDocStrEqual(C.__doc__, "C(x:int=3)")
- def test_docstring_one_field_with_default_none(self):
- @dataclass
- class C:
- x: Union[int, type(None)] = None
- self.assertDocStrEqual(C.__doc__, "C(x:Optional[int]=None)")
- def test_docstring_list_field(self):
- @dataclass
- class C:
- x: List[int]
- self.assertDocStrEqual(C.__doc__, "C(x:List[int])")
- def test_docstring_list_field_with_default_factory(self):
- @dataclass
- class C:
- x: List[int] = field(default_factory=list)
- self.assertDocStrEqual(C.__doc__, "C(x:List[int]=<factory>)")
- def test_docstring_deque_field(self):
- @dataclass
- class C:
- x: deque
- self.assertDocStrEqual(C.__doc__, "C(x:collections.deque)")
- def test_docstring_deque_field_with_default_factory(self):
- @dataclass
- class C:
- x: deque = field(default_factory=deque)
- self.assertDocStrEqual(C.__doc__, "C(x:collections.deque=<factory>)")
- class TestInit(unittest.TestCase):
- def test_base_has_init(self):
- class B:
- def __init__(self):
- self.z = 100
- pass
- # Make sure that declaring this class doesn't raise an error.
- # The issue is that we can't override __init__ in our class,
- # but it should be okay to add __init__ to us if our base has
- # an __init__.
- @dataclass
- class C(B):
- x: int = 0
- c = C(10)
- self.assertEqual(c.x, 10)
- self.assertNotIn('z', vars(c))
- # Make sure that if we don't add an init, the base __init__
- # gets called.
- @dataclass(init=False)
- class C(B):
- x: int = 10
- c = C()
- self.assertEqual(c.x, 10)
- self.assertEqual(c.z, 100)
- def test_no_init(self):
- @dataclass(init=False)
- class C:
- i: int = 0
- self.assertEqual(C().i, 0)
- @dataclass(init=False)
- class C:
- i: int = 2
- def __init__(self):
- self.i = 3
- self.assertEqual(C().i, 3)
- def test_overwriting_init(self):
- # If the class has __init__, use it no matter the value of
- # init=.
- @dataclass
- class C:
- x: int
- def __init__(self, x):
- self.x = 2 * x
- self.assertEqual(C(3).x, 6)
- @dataclass(init=True)
- class C:
- x: int
- def __init__(self, x):
- self.x = 2 * x
- self.assertEqual(C(4).x, 8)
- @dataclass(init=False)
- class C:
- x: int
- def __init__(self, x):
- self.x = 2 * x
- self.assertEqual(C(5).x, 10)
- def test_inherit_from_protocol(self):
- # Dataclasses inheriting from protocol should preserve their own `__init__`.
- # See bpo-45081.
- class P(Protocol):
- a: int
- @dataclass
- class C(P):
- a: int
- self.assertEqual(C(5).a, 5)
- @dataclass
- class D(P):
- def __init__(self, a):
- self.a = a * 2
- self.assertEqual(D(5).a, 10)
- class TestRepr(unittest.TestCase):
- def test_repr(self):
- @dataclass
- class B:
- x: int
- @dataclass
- class C(B):
- y: int = 10
- o = C(4)
- self.assertEqual(repr(o), 'TestRepr.test_repr.<locals>.C(x=4, y=10)')
- @dataclass
- class D(C):
- x: int = 20
- self.assertEqual(repr(D()), 'TestRepr.test_repr.<locals>.D(x=20, y=10)')
- @dataclass
- class C:
- @dataclass
- class D:
- i: int
- @dataclass
- class E:
- pass
- self.assertEqual(repr(C.D(0)), 'TestRepr.test_repr.<locals>.C.D(i=0)')
- self.assertEqual(repr(C.E()), 'TestRepr.test_repr.<locals>.C.E()')
- def test_no_repr(self):
- # Test a class with no __repr__ and repr=False.
- @dataclass(repr=False)
- class C:
- x: int
- self.assertIn(f'{__name__}.TestRepr.test_no_repr.<locals>.C object at',
- repr(C(3)))
- # Test a class with a __repr__ and repr=False.
- @dataclass(repr=False)
- class C:
- x: int
- def __repr__(self):
- return 'C-class'
- self.assertEqual(repr(C(3)), 'C-class')
- def test_overwriting_repr(self):
- # If the class has __repr__, use it no matter the value of
- # repr=.
- @dataclass
- class C:
- x: int
- def __repr__(self):
- return 'x'
- self.assertEqual(repr(C(0)), 'x')
- @dataclass(repr=True)
- class C:
- x: int
- def __repr__(self):
- return 'x'
- self.assertEqual(repr(C(0)), 'x')
- @dataclass(repr=False)
- class C:
- x: int
- def __repr__(self):
- return 'x'
- self.assertEqual(repr(C(0)), 'x')
- class TestEq(unittest.TestCase):
- def test_no_eq(self):
- # Test a class with no __eq__ and eq=False.
- @dataclass(eq=False)
- class C:
- x: int
- self.assertNotEqual(C(0), C(0))
- c = C(3)
- self.assertEqual(c, c)
- # Test a class with an __eq__ and eq=False.
- @dataclass(eq=False)
- class C:
- x: int
- def __eq__(self, other):
- return other == 10
- self.assertEqual(C(3), 10)
- def test_overwriting_eq(self):
- # If the class has __eq__, use it no matter the value of
- # eq=.
- @dataclass
- class C:
- x: int
- def __eq__(self, other):
- return other == 3
- self.assertEqual(C(1), 3)
- self.assertNotEqual(C(1), 1)
- @dataclass(eq=True)
- class C:
- x: int
- def __eq__(self, other):
- return other == 4
- self.assertEqual(C(1), 4)
- self.assertNotEqual(C(1), 1)
- @dataclass(eq=False)
- class C:
- x: int
- def __eq__(self, other):
- return other == 5
- self.assertEqual(C(1), 5)
- self.assertNotEqual(C(1), 1)
- class TestOrdering(unittest.TestCase):
- def test_functools_total_ordering(self):
- # Test that functools.total_ordering works with this class.
- @total_ordering
- @dataclass
- class C:
- x: int
- def __lt__(self, other):
- # Perform the test "backward", just to make
- # sure this is being called.
- return self.x >= other
- self.assertLess(C(0), -1)
- self.assertLessEqual(C(0), -1)
- self.assertGreater(C(0), 1)
- self.assertGreaterEqual(C(0), 1)
- def test_no_order(self):
- # Test that no ordering functions are added by default.
- @dataclass(order=False)
- class C:
- x: int
- # Make sure no order methods are added.
- self.assertNotIn('__le__', C.__dict__)
- self.assertNotIn('__lt__', C.__dict__)
- self.assertNotIn('__ge__', C.__dict__)
- self.assertNotIn('__gt__', C.__dict__)
- # Test that __lt__ is still called
- @dataclass(order=False)
- class C:
- x: int
- def __lt__(self, other):
- return False
- # Make sure other methods aren't added.
- self.assertNotIn('__le__', C.__dict__)
- self.assertNotIn('__ge__', C.__dict__)
- self.assertNotIn('__gt__', C.__dict__)
- def test_overwriting_order(self):
- with self.assertRaisesRegex(TypeError,
- 'Cannot overwrite attribute __lt__'
- '.*using functools.total_ordering'):
- @dataclass(order=True)
- class C:
- x: int
- def __lt__(self):
- pass
- with self.assertRaisesRegex(TypeError,
- 'Cannot overwrite attribute __le__'
- '.*using functools.total_ordering'):
- @dataclass(order=True)
- class C:
- x: int
- def __le__(self):
- pass
- with self.assertRaisesRegex(TypeError,
- 'Cannot overwrite attribute __gt__'
- '.*using functools.total_ordering'):
- @dataclass(order=True)
- class C:
- x: int
- def __gt__(self):
- pass
- with self.assertRaisesRegex(TypeError,
- 'Cannot overwrite attribute __ge__'
- '.*using functools.total_ordering'):
- @dataclass(order=True)
- class C:
- x: int
- def __ge__(self):
- pass
- class TestHash(unittest.TestCase):
- def test_unsafe_hash(self):
- @dataclass(unsafe_hash=True)
- class C:
- x: int
- y: str
- self.assertEqual(hash(C(1, 'foo')), hash((1, 'foo')))
- def test_hash_rules(self):
- def non_bool(value):
- # Map to something else that's True, but not a bool.
- if value is None:
- return None
- if value:
- return (3,)
- return 0
- def test(case, unsafe_hash, eq, frozen, with_hash, result):
- with self.subTest(case=case, unsafe_hash=unsafe_hash, eq=eq,
- frozen=frozen):
- if result != 'exception':
- if with_hash:
- @dataclass(unsafe_hash=unsafe_hash, eq=eq, frozen=frozen)
- class C:
- def __hash__(self):
- return 0
- else:
- @dataclass(unsafe_hash=unsafe_hash, eq=eq, frozen=frozen)
- class C:
- pass
- # See if the result matches what's expected.
- if result == 'fn':
- # __hash__ contains the function we generated.
- self.assertIn('__hash__', C.__dict__)
- self.assertIsNotNone(C.__dict__['__hash__'])
- elif result == '':
- # __hash__ is not present in our class.
- if not with_hash:
- self.assertNotIn('__hash__', C.__dict__)
- elif result == 'none':
- # __hash__ is set to None.
- self.assertIn('__hash__', C.__dict__)
- self.assertIsNone(C.__dict__['__hash__'])
- elif result == 'exception':
- # Creating the class should cause an exception.
- # This only happens with with_hash==True.
- assert(with_hash)
- with self.assertRaisesRegex(TypeError, 'Cannot overwrite attribute __hash__'):
- @dataclass(unsafe_hash=unsafe_hash, eq=eq, frozen=frozen)
- class C:
- def __hash__(self):
- return 0
- else:
- assert False, f'unknown result {result!r}'
- # There are 8 cases of:
- # unsafe_hash=True/False
- # eq=True/False
- # frozen=True/False
- # And for each of these, a different result if
- # __hash__ is defined or not.
- for case, (unsafe_hash, eq, frozen, res_no_defined_hash, res_defined_hash) in enumerate([
- (False, False, False, '', ''),
- (False, False, True, '', ''),
- (False, True, False, 'none', ''),
- (False, True, True, 'fn', ''),
- (True, False, False, 'fn', 'exception'),
- (True, False, True, 'fn', 'exception'),
- (True, True, False, 'fn', 'exception'),
- (True, True, True, 'fn', 'exception'),
- ], 1):
- test(case, unsafe_hash, eq, frozen, False, res_no_defined_hash)
- test(case, unsafe_hash, eq, frozen, True, res_defined_hash)
- # Test non-bool truth values, too. This is just to
- # make sure the data-driven table in the decorator
- # handles non-bool values.
- test(case, non_bool(unsafe_hash), non_bool(eq), non_bool(frozen), False, res_no_defined_hash)
- test(case, non_bool(unsafe_hash), non_bool(eq), non_bool(frozen), True, res_defined_hash)
- def test_eq_only(self):
- # If a class defines __eq__, __hash__ is automatically added
- # and set to None. This is normal Python behavior, not
- # related to dataclasses. Make sure we don't interfere with
- # that (see bpo=32546).
- @dataclass
- class C:
- i: int
- def __eq__(self, other):
- return self.i == other.i
- self.assertEqual(C(1), C(1))
- self.assertNotEqual(C(1), C(4))
- # And make sure things work in this case if we specify
- # unsafe_hash=True.
- @dataclass(unsafe_hash=True)
- class C:
- i: int
- def __eq__(self, other):
- return self.i == other.i
- self.assertEqual(C(1), C(1.0))
- self.assertEqual(hash(C(1)), hash(C(1.0)))
- # And check that the classes __eq__ is being used, despite
- # specifying eq=True.
- @dataclass(unsafe_hash=True, eq=True)
- class C:
- i: int
- def __eq__(self, other):
- return self.i == 3 and self.i == other.i
- self.assertEqual(C(3), C(3))
- self.assertNotEqual(C(1), C(1))
- self.assertEqual(hash(C(1)), hash(C(1.0)))
- def test_0_field_hash(self):
- @dataclass(frozen=True)
- class C:
- pass
- self.assertEqual(hash(C()), hash(()))
- @dataclass(unsafe_hash=True)
- class C:
- pass
- self.assertEqual(hash(C()), hash(()))
- def test_1_field_hash(self):
- @dataclass(frozen=True)
- class C:
- x: int
- self.assertEqual(hash(C(4)), hash((4,)))
- self.assertEqual(hash(C(42)), hash((42,)))
- @dataclass(unsafe_hash=True)
- class C:
- x: int
- self.assertEqual(hash(C(4)), hash((4,)))
- self.assertEqual(hash(C(42)), hash((42,)))
- def test_hash_no_args(self):
- # Test dataclasses with no hash= argument. This exists to
- # make sure that if the @dataclass parameter name is changed
- # or the non-default hashing behavior changes, the default
- # hashability keeps working the same way.
- class Base:
- def __hash__(self):
- return 301
- # If frozen or eq is None, then use the default value (do not
- # specify any value in the decorator).
- for frozen, eq, base, expected in [
- (None, None, object, 'unhashable'),
- (None, None, Base, 'unhashable'),
- (None, False, object, 'object'),
- (None, False, Base, 'base'),
- (None, True, object, 'unhashable'),
- (None, True, Base, 'unhashable'),
- (False, None, object, 'unhashable'),
- (False, None, Base, 'unhashable'),
- (False, False, object, 'object'),
- (False, False, Base, 'base'),
- (False, True, object, 'unhashable'),
- (False, True, Base, 'unhashable'),
- (True, None, object, 'tuple'),
- (True, None, Base, 'tuple'),
- (True, False, object, 'object'),
- (True, False, Base, 'base'),
- (True, True, object, 'tuple'),
- (True, True, Base, 'tuple'),
- ]:
- with self.subTest(frozen=frozen, eq=eq, base=base, expected=expected):
- # First, create the class.
- if frozen is None and eq is None:
- @dataclass
- class C(base):
- i: int
- elif frozen is None:
- @dataclass(eq=eq)
- class C(base):
- i: int
- elif eq is None:
- @dataclass(frozen=frozen)
- class C(base):
- i: int
- else:
- @dataclass(frozen=frozen, eq=eq)
- class C(base):
- i: int
- # Now, make sure it hashes as expected.
- if expected == 'unhashable':
- c = C(10)
- with self.assertRaisesRegex(TypeError, 'unhashable type'):
- hash(c)
- elif expected == 'base':
- self.assertEqual(hash(C(10)), 301)
- elif expected == 'object':
- # I'm not sure what test to use here. object's
- # hash isn't based on id(), so calling hash()
- # won't tell us much. So, just check the
- # function used is object's.
- self.assertIs(C.__hash__, object.__hash__)
- elif expected == 'tuple':
- self.assertEqual(hash(C(42)), hash((42,)))
- else:
- assert False, f'unknown value for expected={expected!r}'
- class TestFrozen(unittest.TestCase):
- def test_frozen(self):
- @dataclass(frozen=True)
- class C:
- i: int
- c = C(10)
- self.assertEqual(c.i, 10)
- with self.assertRaises(FrozenInstanceError):
- c.i = 5
- self.assertEqual(c.i, 10)
- def test_inherit(self):
- @dataclass(frozen=True)
- class C:
- i: int
- @dataclass(frozen=True)
- class D(C):
- j: int
- d = D(0, 10)
- with self.assertRaises(FrozenInstanceError):
- d.i = 5
- with self.assertRaises(FrozenInstanceError):
- d.j = 6
- self.assertEqual(d.i, 0)
- self.assertEqual(d.j, 10)
- def test_inherit_nonfrozen_from_empty_frozen(self):
- @dataclass(frozen=True)
- class C:
- pass
- with self.assertRaisesRegex(TypeError,
- 'cannot inherit non-frozen dataclass from a frozen one'):
- @dataclass
- class D(C):
- j: int
- def test_inherit_nonfrozen_from_empty(self):
- @dataclass
- class C:
- pass
- @dataclass
- class D(C):
- j: int
- d = D(3)
- self.assertEqual(d.j, 3)
- self.assertIsInstance(d, C)
- # Test both ways: with an intermediate normal (non-dataclass)
- # class and without an intermediate class.
- def test_inherit_nonfrozen_from_frozen(self):
- for intermediate_class in [True, False]:
- with self.subTest(intermediate_class=intermediate_class):
- @dataclass(frozen=True)
- class C:
- i: int
- if intermediate_class:
- class I(C): pass
- else:
- I = C
- with self.assertRaisesRegex(TypeError,
- 'cannot inherit non-frozen dataclass from a frozen one'):
- @dataclass
- class D(I):
- pass
- def test_inherit_frozen_from_nonfrozen(self):
- for intermediate_class in [True, False]:
- with self.subTest(intermediate_class=intermediate_class):
- @dataclass
- class C:
- i: int
- if intermediate_class:
- class I(C): pass
- else:
- I = C
- with self.assertRaisesRegex(TypeError,
- 'cannot inherit frozen dataclass from a non-frozen one'):
- @dataclass(frozen=True)
- class D(I):
- pass
- def test_inherit_from_normal_class(self):
- for intermediate_class in [True, False]:
- with self.subTest(intermediate_class=intermediate_class):
- class C:
- pass
- if intermediate_class:
- class I(C): pass
- else:
- I = C
- @dataclass(frozen=True)
- class D(I):
- i: int
- d = D(10)
- with self.assertRaises(FrozenInstanceError):
- d.i = 5
- def test_non_frozen_normal_derived(self):
- # See bpo-32953.
- @dataclass(frozen=True)
- class D:
- x: int
- y: int = 10
- class S(D):
- pass
- s = S(3)
- self.assertEqual(s.x, 3)
- self.assertEqual(s.y, 10)
- s.cached = True
- # But can't change the frozen attributes.
- with self.assertRaises(FrozenInstanceError):
- s.x = 5
- with self.assertRaises(FrozenInstanceError):
- s.y = 5
- self.assertEqual(s.x, 3)
- self.assertEqual(s.y, 10)
- self.assertEqual(s.cached, True)
- def test_overwriting_frozen(self):
- # frozen uses __setattr__ and __delattr__.
- with self.assertRaisesRegex(TypeError,
- 'Cannot overwrite attribute __setattr__'):
- @dataclass(frozen=True)
- class C:
- x: int
- def __setattr__(self):
- pass
- with self.assertRaisesRegex(TypeError,
- 'Cannot overwrite attribute __delattr__'):
- @dataclass(frozen=True)
- class C:
- x: int
- def __delattr__(self):
- pass
- @dataclass(frozen=False)
- class C:
- x: int
- def __setattr__(self, name, value):
- self.__dict__['x'] = value * 2
- self.assertEqual(C(10).x, 20)
- def test_frozen_hash(self):
- @dataclass(frozen=True)
- class C:
- x: Any
- # If x is immutable, we can compute the hash. No exception is
- # raised.
- hash(C(3))
- # If x is mutable, computing the hash is an error.
- with self.assertRaisesRegex(TypeError, 'unhashable type'):
- hash(C({}))
- class TestSlots(unittest.TestCase):
- def test_simple(self):
- @dataclass
- class C:
- __slots__ = ('x',)
- x: Any
- # There was a bug where a variable in a slot was assumed to
- # also have a default value (of type
- # types.MemberDescriptorType).
- with self.assertRaisesRegex(TypeError,
- r"__init__\(\) missing 1 required positional argument: 'x'"):
- C()
- # We can create an instance, and assign to x.
- c = C(10)
- self.assertEqual(c.x, 10)
- c.x = 5
- self.assertEqual(c.x, 5)
- # We can't assign to anything else.
- with self.assertRaisesRegex(AttributeError, "'C' object has no attribute 'y'"):
- c.y = 5
- def test_derived_added_field(self):
- # See bpo-33100.
- @dataclass
- class Base:
- __slots__ = ('x',)
- x: Any
- @dataclass
- class Derived(Base):
- x: int
- y: int
- d = Derived(1, 2)
- self.assertEqual((d.x, d.y), (1, 2))
- # We can add a new field to the derived instance.
- d.z = 10
- def test_generated_slots(self):
- @dataclass(slots=True)
- class C:
- x: int
- y: int
- c = C(1, 2)
- self.assertEqual((c.x, c.y), (1, 2))
- c.x = 3
- c.y = 4
- self.assertEqual((c.x, c.y), (3, 4))
- with self.assertRaisesRegex(AttributeError, "'C' object has no attribute 'z'"):
- c.z = 5
- def test_add_slots_when_slots_exists(self):
- with self.assertRaisesRegex(TypeError, '^C already specifies __slots__$'):
- @dataclass(slots=True)
- class C:
- __slots__ = ('x',)
- x: int
- def test_generated_slots_value(self):
- class Root:
- __slots__ = {'x'}
- class Root2(Root):
- __slots__ = {'k': '...', 'j': ''}
- class Root3(Root2):
- __slots__ = ['h']
- class Root4(Root3):
- __slots__ = 'aa'
- @dataclass(slots=True)
- class Base(Root4):
- y: int
- j: str
- h: str
- self.assertEqual(Base.__slots__, ('y', ))
- @dataclass(slots=True)
- class Derived(Base):
- aa: float
- x: str
- z: int
- k: str
- h: str
- self.assertEqual(Derived.__slots__, ('z', ))
- @dataclass
- class AnotherDerived(Base):
- z: int
- self.assertNotIn('__slots__', AnotherDerived.__dict__)
- def test_cant_inherit_from_iterator_slots(self):
- class Root:
- __slots__ = iter(['a'])
- class Root2(Root):
- __slots__ = ('b', )
- with self.assertRaisesRegex(
- TypeError,
- "^Slots of 'Root' cannot be determined"
- ):
- @dataclass(slots=True)
- class C(Root2):
- x: int
- def test_returns_new_class(self):
- class A:
- x: int
- B = dataclass(A, slots=True)
- self.assertIsNot(A, B)
- self.assertFalse(hasattr(A, "__slots__"))
- self.assertTrue(hasattr(B, "__slots__"))
- # Can't be local to test_frozen_pickle.
- @dataclass(frozen=True, slots=True)
- class FrozenSlotsClass:
- foo: str
- bar: int
- @dataclass(frozen=True)
- class FrozenWithoutSlotsClass:
- foo: str
- bar: int
- def test_frozen_pickle(self):
- # bpo-43999
- self.assertEqual(self.FrozenSlotsClass.__slots__, ("foo", "bar"))
- for proto in range(pickle.HIGHEST_PROTOCOL + 1):
- with self.subTest(proto=proto):
- obj = self.FrozenSlotsClass("a", 1)
- p = pickle.loads(pickle.dumps(obj, protocol=proto))
- self.assertIsNot(obj, p)
- self.assertEqual(obj, p)
- obj = self.FrozenWithoutSlotsClass("a", 1)
- p = pickle.loads(pickle.dumps(obj, protocol=proto))
- self.assertIsNot(obj, p)
- self.assertEqual(obj, p)
- def test_slots_with_default_no_init(self):
- # Originally reported in bpo-44649.
- @dataclass(slots=True)
- class A:
- a: str
- b: str = field(default='b', init=False)
- obj = A("a")
- self.assertEqual(obj.a, 'a')
- self.assertEqual(obj.b, 'b')
- def test_slots_with_default_factory_no_init(self):
- # Originally reported in bpo-44649.
- @dataclass(slots=True)
- class A:
- a: str
- b: str = field(default_factory=lambda:'b', init=False)
- obj = A("a")
- self.assertEqual(obj.a, 'a')
- self.assertEqual(obj.b, 'b')
- def test_slots_no_weakref(self):
- @dataclass(slots=True)
- class A:
- # No weakref.
- pass
- self.assertNotIn("__weakref__", A.__slots__)
- a = A()
- with self.assertRaisesRegex(TypeError,
- "cannot create weak reference"):
- weakref.ref(a)
- def test_slots_weakref(self):
- @dataclass(slots=True, weakref_slot=True)
- class A:
- a: int
- self.assertIn("__weakref__", A.__slots__)
- a = A(1)
- weakref.ref(a)
- def test_slots_weakref_base_str(self):
- class Base:
- __slots__ = '__weakref__'
- @dataclass(slots=True)
- class A(Base):
- a: int
- # __weakref__ is in the base class, not A. But an A is still weakref-able.
- self.assertIn("__weakref__", Base.__slots__)
- self.assertNotIn("__weakref__", A.__slots__)
- a = A(1)
- weakref.ref(a)
- def test_slots_weakref_base_tuple(self):
- # Same as test_slots_weakref_base, but use a tuple instead of a string
- # in the base class.
- class Base:
- __slots__ = ('__weakref__',)
- @dataclass(slots=True)
- class A(Base):
- a: int
- # __weakref__ is in the base class, not A. But an A is still
- # weakref-able.
- self.assertIn("__weakref__", Base.__slots__)
- self.assertNotIn("__weakref__", A.__slots__)
- a = A(1)
- weakref.ref(a)
- def test_weakref_slot_without_slot(self):
- with self.assertRaisesRegex(TypeError,
- "weakref_slot is True but slots is False"):
- @dataclass(weakref_slot=True)
- class A:
- a: int
- def test_weakref_slot_make_dataclass(self):
- A = make_dataclass('A', [('a', int),], slots=True, weakref_slot=True)
- self.assertIn("__weakref__", A.__slots__)
- a = A(1)
- weakref.ref(a)
- # And make sure if raises if slots=True is not given.
- with self.assertRaisesRegex(TypeError,
- "weakref_slot is True but slots is False"):
- B = make_dataclass('B', [('a', int),], weakref_slot=True)
- def test_weakref_slot_subclass_weakref_slot(self):
- @dataclass(slots=True, weakref_slot=True)
- class Base:
- field: int
- # A *can* also specify weakref_slot=True if it wants to (gh-93521)
- @dataclass(slots=True, weakref_slot=True)
- class A(Base):
- ...
- # __weakref__ is in the base class, not A. But an instance of A
- # is still weakref-able.
- self.assertIn("__weakref__", Base.__slots__)
- self.assertNotIn("__weakref__", A.__slots__)
- a = A(1)
- weakref.ref(a)
- def test_weakref_slot_subclass_no_weakref_slot(self):
- @dataclass(slots=True, weakref_slot=True)
- class Base:
- field: int
- @dataclass(slots=True)
- class A(Base):
- ...
- # __weakref__ is in the base class, not A. Even though A doesn't
- # specify weakref_slot, it should still be weakref-able.
- self.assertIn("__weakref__", Base.__slots__)
- self.assertNotIn("__weakref__", A.__slots__)
- a = A(1)
- weakref.ref(a)
- def test_weakref_slot_normal_base_weakref_slot(self):
- class Base:
- __slots__ = ('__weakref__',)
- @dataclass(slots=True, weakref_slot=True)
- class A(Base):
- field: int
- # __weakref__ is in the base class, not A. But an instance of
- # A is still weakref-able.
- self.assertIn("__weakref__", Base.__slots__)
- self.assertNotIn("__weakref__", A.__slots__)
- a = A(1)
- weakref.ref(a)
- class TestDescriptors(unittest.TestCase):
- def test_set_name(self):
- # See bpo-33141.
- # Create a descriptor.
- class D:
- def __set_name__(self, owner, name):
- self.name = name + 'x'
- def __get__(self, instance, owner):
- if instance is not None:
- return 1
- return self
- # This is the case of just normal descriptor behavior, no
- # dataclass code is involved in initializing the descriptor.
- @dataclass
- class C:
- c: int=D()
- self.assertEqual(C.c.name, 'cx')
- # Now test with a default value and init=False, which is the
- # only time this is really meaningful. If not using
- # init=False, then the descriptor will be overwritten, anyway.
- @dataclass
- class C:
- c: int=field(default=D(), init=False)
- self.assertEqual(C.c.name, 'cx')
- self.assertEqual(C().c, 1)
- def test_non_descriptor(self):
- # PEP 487 says __set_name__ should work on non-descriptors.
- # Create a descriptor.
- class D:
- def __set_name__(self, owner, name):
- self.name = name + 'x'
- @dataclass
- class C:
- c: int=field(default=D(), init=False)
- self.assertEqual(C.c.name, 'cx')
- def test_lookup_on_instance(self):
- # See bpo-33175.
- class D:
- pass
- d = D()
- # Create an attribute on the instance, not type.
- d.__set_name__ = Mock()
- # Make sure d.__set_name__ is not called.
- @dataclass
- class C:
- i: int=field(default=d, init=False)
- self.assertEqual(d.__set_name__.call_count, 0)
- def test_lookup_on_class(self):
- # See bpo-33175.
- class D:
- pass
- D.__set_name__ = Mock()
- # Make sure D.__set_name__ is called.
- @dataclass
- class C:
- i: int=field(default=D(), init=False)
- self.assertEqual(D.__set_name__.call_count, 1)
- def test_init_calls_set(self):
- class D:
- pass
- D.__set__ = Mock()
- @dataclass
- class C:
- i: D = D()
- # Make sure D.__set__ is called.
- D.__set__.reset_mock()
- c = C(5)
- self.assertEqual(D.__set__.call_count, 1)
- def test_getting_field_calls_get(self):
- class D:
- pass
- D.__set__ = Mock()
- D.__get__ = Mock()
- @dataclass
- class C:
- i: D = D()
- c = C(5)
- # Make sure D.__get__ is called.
- D.__get__.reset_mock()
- value = c.i
- self.assertEqual(D.__get__.call_count, 1)
- def test_setting_field_calls_set(self):
- class D:
- pass
- D.__set__ = Mock()
- @dataclass
- class C:
- i: D = D()
- c = C(5)
- # Make sure D.__set__ is called.
- D.__set__.reset_mock()
- c.i = 10
- self.assertEqual(D.__set__.call_count, 1)
- def test_setting_uninitialized_descriptor_field(self):
- class D:
- pass
- D.__set__ = Mock()
- @dataclass
- class C:
- i: D
- # D.__set__ is not called because there's no D instance to call it on
- D.__set__.reset_mock()
- c = C(5)
- self.assertEqual(D.__set__.call_count, 0)
- # D.__set__ still isn't called after setting i to an instance of D
- # because descriptors don't behave like that when stored as instance vars
- c.i = D()
- c.i = 5
- self.assertEqual(D.__set__.call_count, 0)
- def test_default_value(self):
- class D:
- def __get__(self, instance: Any, owner: object) -> int:
- if instance is None:
- return 100
- return instance._x
- def __set__(self, instance: Any, value: int) -> None:
- instance._x = value
- @dataclass
- class C:
- i: D = D()
- c = C()
- self.assertEqual(c.i, 100)
- c = C(5)
- self.assertEqual(c.i, 5)
- def test_no_default_value(self):
- class D:
- def __get__(self, instance: Any, owner: object) -> int:
- if instance is None:
- raise AttributeError()
- return instance._x
- def __set__(self, instance: Any, value: int) -> None:
- instance._x = value
- @dataclass
- class C:
- i: D = D()
- with self.assertRaisesRegex(TypeError, 'missing 1 required positional argument'):
- c = C()
- class TestStringAnnotations(unittest.TestCase):
- def test_classvar(self):
- # Some expressions recognized as ClassVar really aren't. But
- # if you're using string annotations, it's not an exact
- # science.
- # These tests assume that both "import typing" and "from
- # typing import *" have been run in this file.
- for typestr in ('ClassVar[int]',
- 'ClassVar [int]',
- ' ClassVar [int]',
- 'ClassVar',
- ' ClassVar ',
- 'typing.ClassVar[int]',
- 'typing.ClassVar[str]',
- ' typing.ClassVar[str]',
- 'typing .ClassVar[str]',
- 'typing. ClassVar[str]',
- 'typing.ClassVar [str]',
- 'typing.ClassVar [ str]',
- # Not syntactically valid, but these will
- # be treated as ClassVars.
- 'typing.ClassVar.[int]',
- 'typing.ClassVar+',
- ):
- with self.subTest(typestr=typestr):
- @dataclass
- class C:
- x: typestr
- # x is a ClassVar, so C() takes no args.
- C()
- # And it won't appear in the class's dict because it doesn't
- # have a default.
- self.assertNotIn('x', C.__dict__)
- def test_isnt_classvar(self):
- for typestr in ('CV',
- 't.ClassVar',
- 't.ClassVar[int]',
- 'typing..ClassVar[int]',
- 'Classvar',
- 'Classvar[int]',
- 'typing.ClassVarx[int]',
- 'typong.ClassVar[int]',
- 'dataclasses.ClassVar[int]',
- 'typingxClassVar[str]',
- ):
- with self.subTest(typestr=typestr):
- @dataclass
- class C:
- x: typestr
- # x is not a ClassVar, so C() takes one arg.
- self.assertEqual(C(10).x, 10)
- def test_initvar(self):
- # These tests assume that both "import dataclasses" and "from
- # dataclasses import *" have been run in this file.
- for typestr in ('InitVar[int]',
- 'InitVar [int]'
- ' InitVar [int]',
- 'InitVar',
- ' InitVar ',
- 'dataclasses.InitVar[int]',
- 'dataclasses.InitVar[str]',
- ' dataclasses.InitVar[str]',
- 'dataclasses .InitVar[str]',
- 'dataclasses. InitVar[str]',
- 'dataclasses.InitVar [str]',
- 'dataclasses.InitVar [ str]',
- # Not syntactically valid, but these will
- # be treated as InitVars.
- 'dataclasses.InitVar.[int]',
- 'dataclasses.InitVar+',
- ):
- with self.subTest(typestr=typestr):
- @dataclass
- class C:
- x: typestr
- # x is an InitVar, so doesn't create a member.
- with self.assertRaisesRegex(AttributeError,
- "object has no attribute 'x'"):
- C(1).x
- def test_isnt_initvar(self):
- for typestr in ('IV',
- 'dc.InitVar',
- 'xdataclasses.xInitVar',
- 'typing.xInitVar[int]',
- ):
- with self.subTest(typestr=typestr):
- @dataclass
- class C:
- x: typestr
- # x is not an InitVar, so there will be a member x.
- self.assertEqual(C(10).x, 10)
- def test_classvar_module_level_import(self):
- from test import dataclass_module_1
- from test import dataclass_module_1_str
- from test import dataclass_module_2
- from test import dataclass_module_2_str
- for m in (dataclass_module_1, dataclass_module_1_str,
- dataclass_module_2, dataclass_module_2_str,
- ):
- with self.subTest(m=m):
- # There's a difference in how the ClassVars are
- # interpreted when using string annotations or
- # not. See the imported modules for details.
- if m.USING_STRINGS:
- c = m.CV(10)
- else:
- c = m.CV()
- self.assertEqual(c.cv0, 20)
- # There's a difference in how the InitVars are
- # interpreted when using string annotations or
- # not. See the imported modules for details.
- c = m.IV(0, 1, 2, 3, 4)
- for field_name in ('iv0', 'iv1', 'iv2', 'iv3'):
- with self.subTest(field_name=field_name):
- with self.assertRaisesRegex(AttributeError, f"object has no attribute '{field_name}'"):
- # Since field_name is an InitVar, it's
- # not an instance field.
- getattr(c, field_name)
- if m.USING_STRINGS:
- # iv4 is interpreted as a normal field.
- self.assertIn('not_iv4', c.__dict__)
- self.assertEqual(c.not_iv4, 4)
- else:
- # iv4 is interpreted as an InitVar, so it
- # won't exist on the instance.
- self.assertNotIn('not_iv4', c.__dict__)
- def test_text_annotations(self):
- from test import dataclass_textanno
- self.assertEqual(
- get_type_hints(dataclass_textanno.Bar),
- {'foo': dataclass_textanno.Foo})
- self.assertEqual(
- get_type_hints(dataclass_textanno.Bar.__init__),
- {'foo': dataclass_textanno.Foo,
- 'return': type(None)})
- class TestMakeDataclass(unittest.TestCase):
- def test_simple(self):
- C = make_dataclass('C',
- [('x', int),
- ('y', int, field(default=5))],
- namespace={'add_one': lambda self: self.x + 1})
- c = C(10)
- self.assertEqual((c.x, c.y), (10, 5))
- self.assertEqual(c.add_one(), 11)
- def test_no_mutate_namespace(self):
- # Make sure a provided namespace isn't mutated.
- ns = {}
- C = make_dataclass('C',
- [('x', int),
- ('y', int, field(default=5))],
- namespace=ns)
- self.assertEqual(ns, {})
- def test_base(self):
- class Base1:
- pass
- class Base2:
- pass
- C = make_dataclass('C',
- [('x', int)],
- bases=(Base1, Base2))
- c = C(2)
- self.assertIsInstance(c, C)
- self.assertIsInstance(c, Base1)
- self.assertIsInstance(c, Base2)
- def test_base_dataclass(self):
- @dataclass
- class Base1:
- x: int
- class Base2:
- pass
- C = make_dataclass('C',
- [('y', int)],
- bases=(Base1, Base2))
- with self.assertRaisesRegex(TypeError, 'required positional'):
- c = C(2)
- c = C(1, 2)
- self.assertIsInstance(c, C)
- self.assertIsInstance(c, Base1)
- self.assertIsInstance(c, Base2)
- self.assertEqual((c.x, c.y), (1, 2))
- def test_init_var(self):
- def post_init(self, y):
- self.x *= y
- C = make_dataclass('C',
- [('x', int),
- ('y', InitVar[int]),
- ],
- namespace={'__post_init__': post_init},
- )
- c = C(2, 3)
- self.assertEqual(vars(c), {'x': 6})
- self.assertEqual(len(fields(c)), 1)
- def test_class_var(self):
- C = make_dataclass('C',
- [('x', int),
- ('y', ClassVar[int], 10),
- ('z', ClassVar[int], field(default=20)),
- ])
- c = C(1)
- self.assertEqual(vars(c), {'x': 1})
- self.assertEqual(len(fields(c)), 1)
- self.assertEqual(C.y, 10)
- self.assertEqual(C.z, 20)
- def test_other_params(self):
- C = make_dataclass('C',
- [('x', int),
- ('y', ClassVar[int], 10),
- ('z', ClassVar[int], field(default=20)),
- ],
- init=False)
- # Make sure we have a repr, but no init.
- self.assertNotIn('__init__', vars(C))
- self.assertIn('__repr__', vars(C))
- # Make sure random other params don't work.
- with self.assertRaisesRegex(TypeError, 'unexpected keyword argument'):
- C = make_dataclass('C',
- [],
- xxinit=False)
- def test_no_types(self):
- C = make_dataclass('Point', ['x', 'y', 'z'])
- c = C(1, 2, 3)
- self.assertEqual(vars(c), {'x': 1, 'y': 2, 'z': 3})
- self.assertEqual(C.__annotations__, {'x': 'typing.Any',
- 'y': 'typing.Any',
- 'z': 'typing.Any'})
- C = make_dataclass('Point', ['x', ('y', int), 'z'])
- c = C(1, 2, 3)
- self.assertEqual(vars(c), {'x': 1, 'y': 2, 'z': 3})
- self.assertEqual(C.__annotations__, {'x': 'typing.Any',
- 'y': int,
- 'z': 'typing.Any'})
- def test_invalid_type_specification(self):
- for bad_field in [(),
- (1, 2, 3, 4),
- ]:
- with self.subTest(bad_field=bad_field):
- with self.assertRaisesRegex(TypeError, r'Invalid field: '):
- make_dataclass('C', ['a', bad_field])
- # And test for things with no len().
- for bad_field in [float,
- lambda x:x,
- ]:
- with self.subTest(bad_field=bad_field):
- with self.assertRaisesRegex(TypeError, r'has no len\(\)'):
- make_dataclass('C', ['a', bad_field])
- def test_duplicate_field_names(self):
- for field in ['a', 'ab']:
- with self.subTest(field=field):
- with self.assertRaisesRegex(TypeError, 'Field name duplicated'):
- make_dataclass('C', [field, 'a', field])
- def test_keyword_field_names(self):
- for field in ['for', 'async', 'await', 'as']:
- with self.subTest(field=field):
- with self.assertRaisesRegex(TypeError, 'must not be keywords'):
- make_dataclass('C', ['a', field])
- with self.assertRaisesRegex(TypeError, 'must not be keywords'):
- make_dataclass('C', [field])
- with self.assertRaisesRegex(TypeError, 'must not be keywords'):
- make_dataclass('C', [field, 'a'])
- def test_non_identifier_field_names(self):
- for field in ['()', 'x,y', '*', '2@3', '', 'little johnny tables']:
- with self.subTest(field=field):
- with self.assertRaisesRegex(TypeError, 'must be valid identifiers'):
- make_dataclass('C', ['a', field])
- with self.assertRaisesRegex(TypeError, 'must be valid identifiers'):
- make_dataclass('C', [field])
- with self.assertRaisesRegex(TypeError, 'must be valid identifiers'):
- make_dataclass('C', [field, 'a'])
- def test_underscore_field_names(self):
- # Unlike namedtuple, it's okay if dataclass field names have
- # an underscore.
- make_dataclass('C', ['_', '_a', 'a_a', 'a_'])
- def test_funny_class_names_names(self):
- # No reason to prevent weird class names, since
- # types.new_class allows them.
- for classname in ['()', 'x,y', '*', '2@3', '']:
- with self.subTest(classname=classname):
- C = make_dataclass(classname, ['a', 'b'])
- self.assertEqual(C.__name__, classname)
- class TestReplace(unittest.TestCase):
- def test(self):
- @dataclass(frozen=True)
- class C:
- x: int
- y: int
- c = C(1, 2)
- c1 = replace(c, x=3)
- self.assertEqual(c1.x, 3)
- self.assertEqual(c1.y, 2)
- def test_frozen(self):
- @dataclass(frozen=True)
- class C:
- x: int
- y: int
- z: int = field(init=False, default=10)
- t: int = field(init=False, default=100)
- c = C(1, 2)
- c1 = replace(c, x=3)
- self.assertEqual((c.x, c.y, c.z, c.t), (1, 2, 10, 100))
- self.assertEqual((c1.x, c1.y, c1.z, c1.t), (3, 2, 10, 100))
- with self.assertRaisesRegex(ValueError, 'init=False'):
- replace(c, x=3, z=20, t=50)
- with self.assertRaisesRegex(ValueError, 'init=False'):
- replace(c, z=20)
- replace(c, x=3, z=20, t=50)
- # Make sure the result is still frozen.
- with self.assertRaisesRegex(FrozenInstanceError, "cannot assign to field 'x'"):
- c1.x = 3
- # Make sure we can't replace an attribute that doesn't exist,
- # if we're also replacing one that does exist. Test this
- # here, because setting attributes on frozen instances is
- # handled slightly differently from non-frozen ones.
- with self.assertRaisesRegex(TypeError, r"__init__\(\) got an unexpected "
- "keyword argument 'a'"):
- c1 = replace(c, x=20, a=5)
- def test_invalid_field_name(self):
- @dataclass(frozen=True)
- class C:
- x: int
- y: int
- c = C(1, 2)
- with self.assertRaisesRegex(TypeError, r"__init__\(\) got an unexpected "
- "keyword argument 'z'"):
- c1 = replace(c, z=3)
- def test_invalid_object(self):
- @dataclass(frozen=True)
- class C:
- x: int
- y: int
- with self.assertRaisesRegex(TypeError, 'dataclass instance'):
- replace(C, x=3)
- with self.assertRaisesRegex(TypeError, 'dataclass instance'):
- replace(0, x=3)
- def test_no_init(self):
- @dataclass
- class C:
- x: int
- y: int = field(init=False, default=10)
- c = C(1)
- c.y = 20
- # Make sure y gets the default value.
- c1 = replace(c, x=5)
- self.assertEqual((c1.x, c1.y), (5, 10))
- # Trying to replace y is an error.
- with self.assertRaisesRegex(ValueError, 'init=False'):
- replace(c, x=2, y=30)
- with self.assertRaisesRegex(ValueError, 'init=False'):
- replace(c, y=30)
- def test_classvar(self):
- @dataclass
- class C:
- x: int
- y: ClassVar[int] = 1000
- c = C(1)
- d = C(2)
- self.assertIs(c.y, d.y)
- self.assertEqual(c.y, 1000)
- # Trying to replace y is an error: can't replace ClassVars.
- with self.assertRaisesRegex(TypeError, r"__init__\(\) got an "
- "unexpected keyword argument 'y'"):
- replace(c, y=30)
- replace(c, x=5)
- def test_initvar_is_specified(self):
- @dataclass
- class C:
- x: int
- y: InitVar[int]
- def __post_init__(self, y):
- self.x *= y
- c = C(1, 10)
- self.assertEqual(c.x, 10)
- with self.assertRaisesRegex(ValueError, r"InitVar 'y' must be "
- "specified with replace()"):
- replace(c, x=3)
- c = replace(c, x=3, y=5)
- self.assertEqual(c.x, 15)
- def test_initvar_with_default_value(self):
- @dataclass
- class C:
- x: int
- y: InitVar[int] = None
- z: InitVar[int] = 42
- def __post_init__(self, y, z):
- if y is not None:
- self.x += y
- if z is not None:
- self.x += z
- c = C(x=1, y=10, z=1)
- self.assertEqual(replace(c), C(x=12))
- self.assertEqual(replace(c, y=4), C(x=12, y=4, z=42))
- self.assertEqual(replace(c, y=4, z=1), C(x=12, y=4, z=1))
- def test_recursive_repr(self):
- @dataclass
- class C:
- f: "C"
- c = C(None)
- c.f = c
- self.assertEqual(repr(c), "TestReplace.test_recursive_repr.<locals>.C(f=...)")
- def test_recursive_repr_two_attrs(self):
- @dataclass
- class C:
- f: "C"
- g: "C"
- c = C(None, None)
- c.f = c
- c.g = c
- self.assertEqual(repr(c), "TestReplace.test_recursive_repr_two_attrs"
- ".<locals>.C(f=..., g=...)")
- def test_recursive_repr_indirection(self):
- @dataclass
- class C:
- f: "D"
- @dataclass
- class D:
- f: "C"
- c = C(None)
- d = D(None)
- c.f = d
- d.f = c
- self.assertEqual(repr(c), "TestReplace.test_recursive_repr_indirection"
- ".<locals>.C(f=TestReplace.test_recursive_repr_indirection"
- ".<locals>.D(f=...))")
- def test_recursive_repr_indirection_two(self):
- @dataclass
- class C:
- f: "D"
- @dataclass
- class D:
- f: "E"
- @dataclass
- class E:
- f: "C"
- c = C(None)
- d = D(None)
- e = E(None)
- c.f = d
- d.f = e
- e.f = c
- self.assertEqual(repr(c), "TestReplace.test_recursive_repr_indirection_two"
- ".<locals>.C(f=TestReplace.test_recursive_repr_indirection_two"
- ".<locals>.D(f=TestReplace.test_recursive_repr_indirection_two"
- ".<locals>.E(f=...)))")
- def test_recursive_repr_misc_attrs(self):
- @dataclass
- class C:
- f: "C"
- g: int
- c = C(None, 1)
- c.f = c
- self.assertEqual(repr(c), "TestReplace.test_recursive_repr_misc_attrs"
- ".<locals>.C(f=..., g=1)")
- ## def test_initvar(self):
- ## @dataclass
- ## class C:
- ## x: int
- ## y: InitVar[int]
- ## c = C(1, 10)
- ## d = C(2, 20)
- ## # In our case, replacing an InitVar is a no-op
- ## self.assertEqual(c, replace(c, y=5))
- ## replace(c, x=5)
- class TestAbstract(unittest.TestCase):
- def test_abc_implementation(self):
- class Ordered(abc.ABC):
- @abc.abstractmethod
- def __lt__(self, other):
- pass
- @abc.abstractmethod
- def __le__(self, other):
- pass
- @dataclass(order=True)
- class Date(Ordered):
- year: int
- month: 'Month'
- day: 'int'
- self.assertFalse(inspect.isabstract(Date))
- self.assertGreater(Date(2020,12,25), Date(2020,8,31))
- def test_maintain_abc(self):
- class A(abc.ABC):
- @abc.abstractmethod
- def foo(self):
- pass
- @dataclass
- class Date(A):
- year: int
- month: 'Month'
- day: 'int'
- self.assertTrue(inspect.isabstract(Date))
- msg = 'class Date with abstract method foo'
- self.assertRaisesRegex(TypeError, msg, Date)
- class TestMatchArgs(unittest.TestCase):
- def test_match_args(self):
- @dataclass
- class C:
- a: int
- self.assertEqual(C(42).__match_args__, ('a',))
- def test_explicit_match_args(self):
- ma = ()
- @dataclass
- class C:
- a: int
- __match_args__ = ma
- self.assertIs(C(42).__match_args__, ma)
- def test_bpo_43764(self):
- @dataclass(repr=False, eq=False, init=False)
- class X:
- a: int
- b: int
- c: int
- self.assertEqual(X.__match_args__, ("a", "b", "c"))
- def test_match_args_argument(self):
- @dataclass(match_args=False)
- class X:
- a: int
- self.assertNotIn('__match_args__', X.__dict__)
- @dataclass(match_args=False)
- class Y:
- a: int
- __match_args__ = ('b',)
- self.assertEqual(Y.__match_args__, ('b',))
- @dataclass(match_args=False)
- class Z(Y):
- z: int
- self.assertEqual(Z.__match_args__, ('b',))
- # Ensure parent dataclass __match_args__ is seen, if child class
- # specifies match_args=False.
- @dataclass
- class A:
- a: int
- z: int
- @dataclass(match_args=False)
- class B(A):
- b: int
- self.assertEqual(B.__match_args__, ('a', 'z'))
- def test_make_dataclasses(self):
- C = make_dataclass('C', [('x', int), ('y', int)])
- self.assertEqual(C.__match_args__, ('x', 'y'))
- C = make_dataclass('C', [('x', int), ('y', int)], match_args=True)
- self.assertEqual(C.__match_args__, ('x', 'y'))
- C = make_dataclass('C', [('x', int), ('y', int)], match_args=False)
- self.assertNotIn('__match__args__', C.__dict__)
- C = make_dataclass('C', [('x', int), ('y', int)], namespace={'__match_args__': ('z',)})
- self.assertEqual(C.__match_args__, ('z',))
- class TestKeywordArgs(unittest.TestCase):
- def test_no_classvar_kwarg(self):
- msg = 'field a is a ClassVar but specifies kw_only'
- with self.assertRaisesRegex(TypeError, msg):
- @dataclass
- class A:
- a: ClassVar[int] = field(kw_only=True)
- with self.assertRaisesRegex(TypeError, msg):
- @dataclass
- class A:
- a: ClassVar[int] = field(kw_only=False)
- with self.assertRaisesRegex(TypeError, msg):
- @dataclass(kw_only=True)
- class A:
- a: ClassVar[int] = field(kw_only=False)
- def test_field_marked_as_kwonly(self):
- #######################
- # Using dataclass(kw_only=True)
- @dataclass(kw_only=True)
- class A:
- a: int
- self.assertTrue(fields(A)[0].kw_only)
- @dataclass(kw_only=True)
- class A:
- a: int = field(kw_only=True)
- self.assertTrue(fields(A)[0].kw_only)
- @dataclass(kw_only=True)
- class A:
- a: int = field(kw_only=False)
- self.assertFalse(fields(A)[0].kw_only)
- #######################
- # Using dataclass(kw_only=False)
- @dataclass(kw_only=False)
- class A:
- a: int
- self.assertFalse(fields(A)[0].kw_only)
- @dataclass(kw_only=False)
- class A:
- a: int = field(kw_only=True)
- self.assertTrue(fields(A)[0].kw_only)
- @dataclass(kw_only=False)
- class A:
- a: int = field(kw_only=False)
- self.assertFalse(fields(A)[0].kw_only)
- #######################
- # Not specifying dataclass(kw_only)
- @dataclass
- class A:
- a: int
- self.assertFalse(fields(A)[0].kw_only)
- @dataclass
- class A:
- a: int = field(kw_only=True)
- self.assertTrue(fields(A)[0].kw_only)
- @dataclass
- class A:
- a: int = field(kw_only=False)
- self.assertFalse(fields(A)[0].kw_only)
- def test_match_args(self):
- # kw fields don't show up in __match_args__.
- @dataclass(kw_only=True)
- class C:
- a: int
- self.assertEqual(C(a=42).__match_args__, ())
- @dataclass
- class C:
- a: int
- b: int = field(kw_only=True)
- self.assertEqual(C(42, b=10).__match_args__, ('a',))
- def test_KW_ONLY(self):
- @dataclass
- class A:
- a: int
- _: KW_ONLY
- b: int
- c: int
- A(3, c=5, b=4)
- msg = "takes 2 positional arguments but 4 were given"
- with self.assertRaisesRegex(TypeError, msg):
- A(3, 4, 5)
- @dataclass(kw_only=True)
- class B:
- a: int
- _: KW_ONLY
- b: int
- c: int
- B(a=3, b=4, c=5)
- msg = "takes 1 positional argument but 4 were given"
- with self.assertRaisesRegex(TypeError, msg):
- B(3, 4, 5)
- # Explicitly make a field that follows KW_ONLY be non-keyword-only.
- @dataclass
- class C:
- a: int
- _: KW_ONLY
- b: int
- c: int = field(kw_only=False)
- c = C(1, 2, b=3)
- self.assertEqual(c.a, 1)
- self.assertEqual(c.b, 3)
- self.assertEqual(c.c, 2)
- c = C(1, b=3, c=2)
- self.assertEqual(c.a, 1)
- self.assertEqual(c.b, 3)
- self.assertEqual(c.c, 2)
- c = C(1, b=3, c=2)
- self.assertEqual(c.a, 1)
- self.assertEqual(c.b, 3)
- self.assertEqual(c.c, 2)
- c = C(c=2, b=3, a=1)
- self.assertEqual(c.a, 1)
- self.assertEqual(c.b, 3)
- self.assertEqual(c.c, 2)
- def test_KW_ONLY_as_string(self):
- @dataclass
- class A:
- a: int
- _: 'dataclasses.KW_ONLY'
- b: int
- c: int
- A(3, c=5, b=4)
- msg = "takes 2 positional arguments but 4 were given"
- with self.assertRaisesRegex(TypeError, msg):
- A(3, 4, 5)
- def test_KW_ONLY_twice(self):
- msg = "'Y' is KW_ONLY, but KW_ONLY has already been specified"
- with self.assertRaisesRegex(TypeError, msg):
- @dataclass
- class A:
- a: int
- X: KW_ONLY
- Y: KW_ONLY
- b: int
- c: int
- with self.assertRaisesRegex(TypeError, msg):
- @dataclass
- class A:
- a: int
- X: KW_ONLY
- b: int
- Y: KW_ONLY
- c: int
- with self.assertRaisesRegex(TypeError, msg):
- @dataclass
- class A:
- a: int
- X: KW_ONLY
- b: int
- c: int
- Y: KW_ONLY
- # But this usage is okay, since it's not using KW_ONLY.
- @dataclass
- class A:
- a: int
- _: KW_ONLY
- b: int
- c: int = field(kw_only=True)
- # And if inheriting, it's okay.
- @dataclass
- class A:
- a: int
- _: KW_ONLY
- b: int
- c: int
- @dataclass
- class B(A):
- _: KW_ONLY
- d: int
- # Make sure the error is raised in a derived class.
- with self.assertRaisesRegex(TypeError, msg):
- @dataclass
- class A:
- a: int
- _: KW_ONLY
- b: int
- c: int
- @dataclass
- class B(A):
- X: KW_ONLY
- d: int
- Y: KW_ONLY
- def test_post_init(self):
- @dataclass
- class A:
- a: int
- _: KW_ONLY
- b: InitVar[int]
- c: int
- d: InitVar[int]
- def __post_init__(self, b, d):
- raise CustomError(f'{b=} {d=}')
- with self.assertRaisesRegex(CustomError, 'b=3 d=4'):
- A(1, c=2, b=3, d=4)
- @dataclass
- class B:
- a: int
- _: KW_ONLY
- b: InitVar[int]
- c: int
- d: InitVar[int]
- def __post_init__(self, b, d):
- self.a = b
- self.c = d
- b = B(1, c=2, b=3, d=4)
- self.assertEqual(asdict(b), {'a': 3, 'c': 4})
- def test_defaults(self):
- # For kwargs, make sure we can have defaults after non-defaults.
- @dataclass
- class A:
- a: int = 0
- _: KW_ONLY
- b: int
- c: int = 1
- d: int
- a = A(d=4, b=3)
- self.assertEqual(a.a, 0)
- self.assertEqual(a.b, 3)
- self.assertEqual(a.c, 1)
- self.assertEqual(a.d, 4)
- # Make sure we still check for non-kwarg non-defaults not following
- # defaults.
- err_regex = "non-default argument 'z' follows default argument"
- with self.assertRaisesRegex(TypeError, err_regex):
- @dataclass
- class A:
- a: int = 0
- z: int
- _: KW_ONLY
- b: int
- c: int = 1
- d: int
- def test_make_dataclass(self):
- A = make_dataclass("A", ['a'], kw_only=True)
- self.assertTrue(fields(A)[0].kw_only)
- B = make_dataclass("B",
- ['a', ('b', int, field(kw_only=False))],
- kw_only=True)
- self.assertTrue(fields(B)[0].kw_only)
- self.assertFalse(fields(B)[1].kw_only)
- if __name__ == '__main__':
- unittest.main()
|