| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312 |
- """
- A lightweight Traits like module.
- This is designed to provide a lightweight, simple, pure Python version of
- many of the capabilities of enthought.traits. This includes:
- * Validation
- * Type specification with defaults
- * Static and dynamic notification
- * Basic predefined types
- * An API that is similar to enthought.traits
- We don't support:
- * Delegation
- * Automatic GUI generation
- * A full set of trait types. Most importantly, we don't provide container
- traits (list, dict, tuple) that can trigger notifications if their
- contents change.
- * API compatibility with enthought.traits
- There are also some important difference in our design:
- * enthought.traits does not validate default values. We do.
- We choose to create this module because we need these capabilities, but
- we need them to be pure Python so they work in all Python implementations,
- including Jython and IronPython.
- Inheritance diagram:
- .. inheritance-diagram:: traitlets.traitlets
- :parts: 3
- """
- # Copyright (c) IPython Development Team.
- # Distributed under the terms of the Modified BSD License.
- #
- # Adapted from enthought.traits, Copyright (c) Enthought, Inc.,
- # also under the terms of the Modified BSD License.
- from __future__ import annotations
- import contextlib
- import enum
- import inspect
- import os
- import re
- import sys
- import types
- import typing as t
- from ast import literal_eval
- from .utils.bunch import Bunch
- from .utils.descriptions import add_article, class_of, describe, repr_type
- from .utils.getargspec import getargspec
- from .utils.importstring import import_item
- from .utils.sentinel import Sentinel
- from .utils.warnings import deprecated_method, should_warn, warn
- SequenceTypes = (list, tuple, set, frozenset)
- # backward compatibility, use to differ between Python 2 and 3.
- ClassTypes = (type,)
- if t.TYPE_CHECKING:
- from typing_extensions import TypeVar
- else:
- from typing import TypeVar
- # exports:
- __all__ = [
- "All",
- "Any",
- "BaseDescriptor",
- "Bool",
- "Bytes",
- "CBool",
- "CBytes",
- "CComplex",
- "CFloat",
- "CInt",
- "CLong",
- "CRegExp",
- "CUnicode",
- "Callable",
- "CaselessStrEnum",
- "ClassBasedTraitType",
- "Complex",
- "Container",
- "DefaultHandler",
- "Dict",
- "DottedObjectName",
- "Enum",
- "EventHandler",
- "Float",
- "ForwardDeclaredInstance",
- "ForwardDeclaredMixin",
- "ForwardDeclaredType",
- "FuzzyEnum",
- "HasDescriptors",
- "HasTraits",
- "Instance",
- "Int",
- "Integer",
- "List",
- "Long",
- "MetaHasDescriptors",
- "MetaHasTraits",
- "ObjectName",
- "ObserveHandler",
- "Set",
- "TCPAddress",
- "This",
- "TraitError",
- "TraitType",
- "Tuple",
- "Type",
- "Unicode",
- "Undefined",
- "Union",
- "UseEnum",
- "ValidateHandler",
- "default",
- "directional_link",
- "dlink",
- "link",
- "observe",
- "observe_compat",
- "parse_notifier_name",
- "validate",
- ]
- # any TraitType subclass (that doesn't start with _) will be added automatically
- # -----------------------------------------------------------------------------
- # Basic classes
- # -----------------------------------------------------------------------------
- Undefined = Sentinel(
- "Undefined",
- "traitlets",
- """
- Used in Traitlets to specify that no defaults are set in kwargs
- """,
- )
- All = Sentinel(
- "All",
- "traitlets",
- """
- Used in Traitlets to listen to all types of notification or to notifications
- from all trait attributes.
- """,
- )
- # Deprecated alias
- NoDefaultSpecified = Undefined
- class TraitError(Exception):
- pass
- # -----------------------------------------------------------------------------
- # Utilities
- # -----------------------------------------------------------------------------
- def isidentifier(s: t.Any) -> bool:
- return t.cast(bool, s.isidentifier())
- def _safe_literal_eval(s: str) -> t.Any:
- """Safely evaluate an expression
- Returns original string if eval fails.
- Use only where types are ambiguous.
- """
- try:
- return literal_eval(s)
- except (NameError, SyntaxError, ValueError):
- return s
- def is_trait(t: t.Any) -> bool:
- """Returns whether the given value is an instance or subclass of TraitType."""
- return isinstance(t, TraitType) or (isinstance(t, type) and issubclass(t, TraitType))
- def parse_notifier_name(names: Sentinel | str | t.Iterable[Sentinel | str]) -> t.Iterable[t.Any]:
- """Convert the name argument to a list of names.
- Examples
- --------
- >>> parse_notifier_name([])
- [traitlets.All]
- >>> parse_notifier_name("a")
- ['a']
- >>> parse_notifier_name(["a", "b"])
- ['a', 'b']
- >>> parse_notifier_name(All)
- [traitlets.All]
- """
- if names is All or isinstance(names, str):
- return [names]
- elif isinstance(names, Sentinel):
- raise TypeError("`names` must be either `All`, a str, or a list of strs.")
- else:
- if not names or All in names:
- return [All]
- for n in names:
- if not isinstance(n, str):
- raise TypeError(f"names must be strings, not {type(n).__name__}({n!r})")
- return names
- class _SimpleTest:
- def __init__(self, value: t.Any) -> None:
- self.value = value
- def __call__(self, test: t.Any) -> bool:
- return bool(test == self.value)
- def __repr__(self) -> str:
- return "<SimpleTest(%r)" % self.value
- def __str__(self) -> str:
- return self.__repr__()
- def getmembers(object: t.Any, predicate: t.Any = None) -> list[tuple[str, t.Any]]:
- """A safe version of inspect.getmembers that handles missing attributes.
- This is useful when there are descriptor based attributes that for
- some reason raise AttributeError even though they exist. This happens
- in zope.interface with the __provides__ attribute.
- """
- results = []
- for key in dir(object):
- try:
- value = getattr(object, key)
- except AttributeError:
- pass
- else:
- if not predicate or predicate(value):
- results.append((key, value))
- results.sort()
- return results
- def _validate_link(*tuples: t.Any) -> None:
- """Validate arguments for traitlet link functions"""
- for tup in tuples:
- if not len(tup) == 2:
- raise TypeError(
- "Each linked traitlet must be specified as (HasTraits, 'trait_name'), not %r" % t
- )
- obj, trait_name = tup
- if not isinstance(obj, HasTraits):
- raise TypeError("Each object must be HasTraits, not %r" % type(obj))
- if trait_name not in obj.traits():
- raise TypeError(f"{obj!r} has no trait {trait_name!r}")
- class link:
- """Link traits from different objects together so they remain in sync.
- Parameters
- ----------
- source : (object / attribute name) pair
- target : (object / attribute name) pair
- transform: iterable with two callables (optional)
- Data transformation between source and target and target and source.
- Examples
- --------
- >>> class X(HasTraits):
- ... value = Int()
- >>> src = X(value=1)
- >>> tgt = X(value=42)
- >>> c = link((src, "value"), (tgt, "value"))
- Setting source updates target objects:
- >>> src.value = 5
- >>> tgt.value
- 5
- """
- updating = False
- def __init__(self, source: t.Any, target: t.Any, transform: t.Any = None) -> None:
- _validate_link(source, target)
- self.source, self.target = source, target
- self._transform, self._transform_inv = transform if transform else (lambda x: x,) * 2
- self.link()
- def link(self) -> None:
- try:
- setattr(
- self.target[0],
- self.target[1],
- self._transform(getattr(self.source[0], self.source[1])),
- )
- finally:
- self.source[0].observe(self._update_target, names=self.source[1])
- self.target[0].observe(self._update_source, names=self.target[1])
- @contextlib.contextmanager
- def _busy_updating(self) -> t.Any:
- self.updating = True
- try:
- yield
- finally:
- self.updating = False
- def _update_target(self, change: t.Any) -> None:
- if self.updating:
- return
- with self._busy_updating():
- setattr(self.target[0], self.target[1], self._transform(change.new))
- if getattr(self.source[0], self.source[1]) != change.new:
- raise TraitError(
- f"Broken link {self}: the source value changed while updating " "the target."
- )
- def _update_source(self, change: t.Any) -> None:
- if self.updating:
- return
- with self._busy_updating():
- setattr(self.source[0], self.source[1], self._transform_inv(change.new))
- if getattr(self.target[0], self.target[1]) != change.new:
- raise TraitError(
- f"Broken link {self}: the target value changed while updating " "the source."
- )
- def unlink(self) -> None:
- self.source[0].unobserve(self._update_target, names=self.source[1])
- self.target[0].unobserve(self._update_source, names=self.target[1])
- class directional_link:
- """Link the trait of a source object with traits of target objects.
- Parameters
- ----------
- source : (object, attribute name) pair
- target : (object, attribute name) pair
- transform: callable (optional)
- Data transformation between source and target.
- Examples
- --------
- >>> class X(HasTraits):
- ... value = Int()
- >>> src = X(value=1)
- >>> tgt = X(value=42)
- >>> c = directional_link((src, "value"), (tgt, "value"))
- Setting source updates target objects:
- >>> src.value = 5
- >>> tgt.value
- 5
- Setting target does not update source object:
- >>> tgt.value = 6
- >>> src.value
- 5
- """
- updating = False
- def __init__(self, source: t.Any, target: t.Any, transform: t.Any = None) -> None:
- self._transform = transform if transform else lambda x: x
- _validate_link(source, target)
- self.source, self.target = source, target
- self.link()
- def link(self) -> None:
- try:
- setattr(
- self.target[0],
- self.target[1],
- self._transform(getattr(self.source[0], self.source[1])),
- )
- finally:
- self.source[0].observe(self._update, names=self.source[1])
- @contextlib.contextmanager
- def _busy_updating(self) -> t.Any:
- self.updating = True
- try:
- yield
- finally:
- self.updating = False
- def _update(self, change: t.Any) -> None:
- if self.updating:
- return
- with self._busy_updating():
- setattr(self.target[0], self.target[1], self._transform(change.new))
- def unlink(self) -> None:
- self.source[0].unobserve(self._update, names=self.source[1])
- dlink = directional_link
- # -----------------------------------------------------------------------------
- # Base Descriptor Class
- # -----------------------------------------------------------------------------
- class BaseDescriptor:
- """Base descriptor class
- Notes
- -----
- This implements Python's descriptor protocol.
- This class is the base class for all such descriptors. The
- only magic we use is a custom metaclass for the main :class:`HasTraits`
- class that does the following:
- 1. Sets the :attr:`name` attribute of every :class:`BaseDescriptor`
- instance in the class dict to the name of the attribute.
- 2. Sets the :attr:`this_class` attribute of every :class:`BaseDescriptor`
- instance in the class dict to the *class* that declared the trait.
- This is used by the :class:`This` trait to allow subclasses to
- accept superclasses for :class:`This` values.
- """
- name: str | None = None
- this_class: type[HasTraits] | None = None
- def class_init(self, cls: type[HasTraits], name: str | None) -> None:
- """Part of the initialization which may depend on the underlying
- HasDescriptors class.
- It is typically overloaded for specific types.
- This method is called by :meth:`MetaHasDescriptors.__init__`
- passing the class (`cls`) and `name` under which the descriptor
- has been assigned.
- """
- self.this_class = cls
- self.name = name
- def subclass_init(self, cls: type[HasTraits]) -> None:
- # Instead of HasDescriptors.setup_instance calling
- # every instance_init, we opt in by default.
- # This gives descriptors a change to opt out for
- # performance reasons.
- # Because most traits do not need instance_init,
- # and it will otherwise be called for every HasTrait instance
- # being created, this otherwise gives a significant performance
- # pentalty. Most TypeTraits in traitlets opt out.
- cls._instance_inits.append(self.instance_init)
- def instance_init(self, obj: t.Any) -> None:
- """Part of the initialization which may depend on the underlying
- HasDescriptors instance.
- It is typically overloaded for specific types.
- This method is called by :meth:`HasTraits.__new__` and in the
- :meth:`BaseDescriptor.instance_init` method of descriptors holding
- other descriptors.
- """
- G = TypeVar("G")
- S = TypeVar("S")
- T = TypeVar("T")
- # Self from typing extension doesn't work well with mypy https://github.com/python/mypy/pull/14041
- # see https://peps.python.org/pep-0673/#use-in-generic-classes
- # Self = t.TypeVar("Self", bound="TraitType[Any, Any]")
- if t.TYPE_CHECKING:
- from typing_extensions import Literal, Self
- K = TypeVar("K", default=str)
- V = TypeVar("V", default=t.Any)
- # We use a type for the getter (G) and setter (G) because we allow
- # for traits to cast (for instance CInt will use G=int, S=t.Any)
- class TraitType(BaseDescriptor, t.Generic[G, S]):
- """A base class for all trait types."""
- metadata: dict[str, t.Any] = {}
- allow_none: bool = False
- read_only: bool = False
- info_text: str = "any value"
- default_value: t.Any = Undefined
- def __init__(
- self: TraitType[G, S],
- default_value: t.Any = Undefined,
- allow_none: bool = False,
- read_only: bool | None = None,
- help: str | None = None,
- config: t.Any = None,
- **kwargs: t.Any,
- ) -> None:
- """Declare a traitlet.
- If *allow_none* is True, None is a valid value in addition to any
- values that are normally valid. The default is up to the subclass.
- For most trait types, the default value for ``allow_none`` is False.
- If *read_only* is True, attempts to directly modify a trait attribute raises a TraitError.
- If *help* is a string, it documents the attribute's purpose.
- Extra metadata can be associated with the traitlet using the .tag() convenience method
- or by using the traitlet instance's .metadata dictionary.
- """
- if default_value is not Undefined:
- self.default_value = default_value
- if allow_none:
- self.allow_none = allow_none
- if read_only is not None:
- self.read_only = read_only
- self.help = help if help is not None else ""
- if self.help:
- # define __doc__ so that inspectors like autodoc find traits
- self.__doc__ = self.help
- if len(kwargs) > 0:
- stacklevel = 1
- f = inspect.currentframe()
- # count supers to determine stacklevel for warning
- assert f is not None
- while f.f_code.co_name == "__init__":
- stacklevel += 1
- f = f.f_back
- assert f is not None
- mod = f.f_globals.get("__name__") or ""
- pkg = mod.split(".", 1)[0]
- key = ("metadata-tag", pkg, *sorted(kwargs))
- if should_warn(key):
- warn(
- f"metadata {kwargs} was set from the constructor. "
- "With traitlets 4.1, metadata should be set using the .tag() method, "
- "e.g., Int().tag(key1='value1', key2='value2')",
- DeprecationWarning,
- stacklevel=stacklevel,
- )
- if len(self.metadata) > 0:
- self.metadata = self.metadata.copy()
- self.metadata.update(kwargs)
- else:
- self.metadata = kwargs
- else:
- self.metadata = self.metadata.copy()
- if config is not None:
- self.metadata["config"] = config
- # We add help to the metadata during a deprecation period so that
- # code that looks for the help string there can find it.
- if help is not None:
- self.metadata["help"] = help
- def from_string(self, s: str) -> G | None:
- """Get a value from a config string
- such as an environment variable or CLI arguments.
- Traits can override this method to define their own
- parsing of config strings.
- .. seealso:: item_from_string
- .. versionadded:: 5.0
- """
- if self.allow_none and s == "None":
- return None
- return s # type:ignore[return-value]
- def default(self, obj: t.Any = None) -> G | None:
- """The default generator for this trait
- Notes
- -----
- This method is registered to HasTraits classes during ``class_init``
- in the same way that dynamic defaults defined by ``@default`` are.
- """
- if self.default_value is not Undefined:
- return t.cast(G, self.default_value)
- elif hasattr(self, "make_dynamic_default"):
- return t.cast(G, self.make_dynamic_default())
- else:
- # Undefined will raise in TraitType.get
- return t.cast(G, self.default_value)
- def get_default_value(self) -> G | None:
- """DEPRECATED: Retrieve the static default value for this trait.
- Use self.default_value instead
- """
- warn(
- "get_default_value is deprecated in traitlets 4.0: use the .default_value attribute",
- DeprecationWarning,
- stacklevel=2,
- )
- return t.cast(G, self.default_value)
- def init_default_value(self, obj: t.Any) -> G | None:
- """DEPRECATED: Set the static default value for the trait type."""
- warn(
- "init_default_value is deprecated in traitlets 4.0, and may be removed in the future",
- DeprecationWarning,
- stacklevel=2,
- )
- value = self._validate(obj, self.default_value)
- obj._trait_values[self.name] = value
- return value
- def get(self, obj: HasTraits, cls: type[t.Any] | None = None) -> G | None:
- assert self.name is not None
- try:
- value = obj._trait_values[self.name]
- except KeyError:
- # Check for a dynamic initializer.
- default = obj.trait_defaults(self.name)
- if default is Undefined:
- warn(
- "Explicit using of Undefined as the default value "
- "is deprecated in traitlets 5.0, and may cause "
- "exceptions in the future.",
- DeprecationWarning,
- stacklevel=2,
- )
- # Using a context manager has a large runtime overhead, so we
- # write out the obj.cross_validation_lock call here.
- _cross_validation_lock = obj._cross_validation_lock
- try:
- obj._cross_validation_lock = True
- value = self._validate(obj, default)
- finally:
- obj._cross_validation_lock = _cross_validation_lock
- obj._trait_values[self.name] = value
- obj._notify_observers(
- Bunch(
- name=self.name,
- value=value,
- owner=obj,
- type="default",
- )
- )
- return t.cast(G, value)
- except Exception as e:
- # This should never be reached.
- raise TraitError("Unexpected error in TraitType: default value not set properly") from e
- else:
- return t.cast(G, value)
- @t.overload
- def __get__(self, obj: None, cls: type[t.Any]) -> Self:
- ...
- @t.overload
- def __get__(self, obj: t.Any, cls: type[t.Any]) -> G:
- ...
- def __get__(self, obj: HasTraits | None, cls: type[t.Any]) -> Self | G:
- """Get the value of the trait by self.name for the instance.
- Default values are instantiated when :meth:`HasTraits.__new__`
- is called. Thus by the time this method gets called either the
- default value or a user defined value (they called :meth:`__set__`)
- is in the :class:`HasTraits` instance.
- """
- if obj is None:
- return self
- else:
- return t.cast(G, self.get(obj, cls)) # the G should encode the Optional
- def set(self, obj: HasTraits, value: S) -> None:
- new_value = self._validate(obj, value)
- assert self.name is not None
- try:
- old_value = obj._trait_values[self.name]
- except KeyError:
- old_value = self.default_value
- obj._trait_values[self.name] = new_value
- try:
- silent = bool(old_value == new_value)
- except Exception:
- # if there is an error in comparing, default to notify
- silent = False
- if silent is not True:
- # we explicitly compare silent to True just in case the equality
- # comparison above returns something other than True/False
- obj._notify_trait(self.name, old_value, new_value)
- def __set__(self, obj: HasTraits, value: S) -> None:
- """Set the value of the trait by self.name for the instance.
- Values pass through a validation stage where errors are raised when
- impropper types, or types that cannot be coerced, are encountered.
- """
- if self.read_only:
- raise TraitError('The "%s" trait is read-only.' % self.name)
- self.set(obj, value)
- def _validate(self, obj: t.Any, value: t.Any) -> G | None:
- if value is None and self.allow_none:
- return value
- if hasattr(self, "validate"):
- value = self.validate(obj, value)
- if obj._cross_validation_lock is False:
- value = self._cross_validate(obj, value)
- return t.cast(G, value)
- def _cross_validate(self, obj: t.Any, value: t.Any) -> G | None:
- if self.name in obj._trait_validators:
- proposal = Bunch({"trait": self, "value": value, "owner": obj})
- value = obj._trait_validators[self.name](obj, proposal)
- elif hasattr(obj, "_%s_validate" % self.name):
- meth_name = "_%s_validate" % self.name
- cross_validate = getattr(obj, meth_name)
- deprecated_method(
- cross_validate,
- obj.__class__,
- meth_name,
- "use @validate decorator instead.",
- )
- value = cross_validate(value, self)
- return t.cast(G, value)
- def __or__(self, other: TraitType[t.Any, t.Any]) -> Union:
- if isinstance(other, Union):
- return Union([self, *other.trait_types])
- else:
- return Union([self, other])
- def info(self) -> str:
- return self.info_text
- def error(
- self,
- obj: HasTraits | None,
- value: t.Any,
- error: Exception | None = None,
- info: str | None = None,
- ) -> t.NoReturn:
- """Raise a TraitError
- Parameters
- ----------
- obj : HasTraits or None
- The instance which owns the trait. If not
- object is given, then an object agnostic
- error will be raised.
- value : any
- The value that caused the error.
- error : Exception (default: None)
- An error that was raised by a child trait.
- The arguments of this exception should be
- of the form ``(value, info, *traits)``.
- Where the ``value`` and ``info`` are the
- problem value, and string describing the
- expected value. The ``traits`` are a series
- of :class:`TraitType` instances that are
- "children" of this one (the first being
- the deepest).
- info : str (default: None)
- A description of the expected value. By
- default this is inferred from this trait's
- ``info`` method.
- """
- if error is not None:
- # handle nested error
- error.args += (self,)
- if self.name is not None:
- # this is the root trait that must format the final message
- chain = " of ".join(describe("a", t) for t in error.args[2:])
- if obj is not None:
- error.args = (
- "The '{}' trait of {} instance contains {} which "
- "expected {}, not {}.".format(
- self.name,
- describe("an", obj),
- chain,
- error.args[1],
- describe("the", error.args[0]),
- ),
- )
- else:
- error.args = (
- "The '{}' trait contains {} which " "expected {}, not {}.".format(
- self.name,
- chain,
- error.args[1],
- describe("the", error.args[0]),
- ),
- )
- raise error
- # this trait caused an error
- if self.name is None:
- # this is not the root trait
- raise TraitError(value, info or self.info(), self)
- # this is the root trait
- if obj is not None:
- e = "The '{}' trait of {} instance expected {}, not {}.".format(
- self.name,
- class_of(obj),
- info or self.info(),
- describe("the", value),
- )
- else:
- e = "The '{}' trait expected {}, not {}.".format(
- self.name,
- info or self.info(),
- describe("the", value),
- )
- raise TraitError(e)
- def get_metadata(self, key: str, default: t.Any = None) -> t.Any:
- """DEPRECATED: Get a metadata value.
- Use .metadata[key] or .metadata.get(key, default) instead.
- """
- if key == "help":
- msg = "use the instance .help string directly, like x.help"
- else:
- msg = "use the instance .metadata dictionary directly, like x.metadata[key] or x.metadata.get(key, default)"
- warn("Deprecated in traitlets 4.1, " + msg, DeprecationWarning, stacklevel=2)
- return self.metadata.get(key, default)
- def set_metadata(self, key: str, value: t.Any) -> None:
- """DEPRECATED: Set a metadata key/value.
- Use .metadata[key] = value instead.
- """
- if key == "help":
- msg = "use the instance .help string directly, like x.help = value"
- else:
- msg = "use the instance .metadata dictionary directly, like x.metadata[key] = value"
- warn("Deprecated in traitlets 4.1, " + msg, DeprecationWarning, stacklevel=2)
- self.metadata[key] = value
- def tag(self, **metadata: t.Any) -> Self:
- """Sets metadata and returns self.
- This allows convenient metadata tagging when initializing the trait, such as:
- Examples
- --------
- >>> Int(0).tag(config=True, sync=True)
- <traitlets.traitlets.Int object at ...>
- """
- maybe_constructor_keywords = set(metadata.keys()).intersection(
- {"help", "allow_none", "read_only", "default_value"}
- )
- if maybe_constructor_keywords:
- warn(
- "The following attributes are set in using `tag`, but seem to be constructor keywords arguments: %s "
- % maybe_constructor_keywords,
- UserWarning,
- stacklevel=2,
- )
- self.metadata.update(metadata)
- return self
- def default_value_repr(self) -> str:
- return repr(self.default_value)
- # -----------------------------------------------------------------------------
- # The HasTraits implementation
- # -----------------------------------------------------------------------------
- class _CallbackWrapper:
- """An object adapting a on_trait_change callback into an observe callback.
- The comparison operator __eq__ is implemented to enable removal of wrapped
- callbacks.
- """
- def __init__(self, cb: t.Any) -> None:
- self.cb = cb
- # Bound methods have an additional 'self' argument.
- offset = -1 if isinstance(self.cb, types.MethodType) else 0
- self.nargs = len(getargspec(cb)[0]) + offset
- if self.nargs > 4:
- raise TraitError("a trait changed callback must have 0-4 arguments.")
- def __eq__(self, other: object) -> bool:
- # The wrapper is equal to the wrapped element
- if isinstance(other, _CallbackWrapper):
- return bool(self.cb == other.cb)
- else:
- return bool(self.cb == other)
- def __call__(self, change: Bunch) -> None:
- # The wrapper is callable
- if self.nargs == 0:
- self.cb()
- elif self.nargs == 1:
- self.cb(change.name)
- elif self.nargs == 2:
- self.cb(change.name, change.new)
- elif self.nargs == 3:
- self.cb(change.name, change.old, change.new)
- elif self.nargs == 4:
- self.cb(change.name, change.old, change.new, change.owner)
- def _callback_wrapper(cb: t.Any) -> _CallbackWrapper:
- if isinstance(cb, _CallbackWrapper):
- return cb
- else:
- return _CallbackWrapper(cb)
- class MetaHasDescriptors(type):
- """A metaclass for HasDescriptors.
- This metaclass makes sure that any TraitType class attributes are
- instantiated and sets their name attribute.
- """
- def __new__(
- mcls: type[MetaHasDescriptors],
- name: str,
- bases: tuple[type, ...],
- classdict: dict[str, t.Any],
- **kwds: t.Any,
- ) -> MetaHasDescriptors:
- """Create the HasDescriptors class."""
- for k, v in classdict.items():
- # ----------------------------------------------------------------
- # Support of deprecated behavior allowing for TraitType types
- # to be used instead of TraitType instances.
- if inspect.isclass(v) and issubclass(v, TraitType):
- warn(
- "Traits should be given as instances, not types (for example, `Int()`, not `Int`)."
- " Passing types is deprecated in traitlets 4.1.",
- DeprecationWarning,
- stacklevel=2,
- )
- classdict[k] = v()
- # ----------------------------------------------------------------
- return super().__new__(mcls, name, bases, classdict, **kwds)
- def __init__(
- cls, name: str, bases: tuple[type, ...], classdict: dict[str, t.Any], **kwds: t.Any
- ) -> None:
- """Finish initializing the HasDescriptors class."""
- super().__init__(name, bases, classdict, **kwds)
- cls.setup_class(classdict)
- def setup_class(cls: MetaHasDescriptors, classdict: dict[str, t.Any]) -> None:
- """Setup descriptor instance on the class
- This sets the :attr:`this_class` and :attr:`name` attributes of each
- BaseDescriptor in the class dict of the newly created ``cls`` before
- calling their :attr:`class_init` method.
- """
- cls._descriptors = []
- cls._instance_inits: list[t.Any] = []
- for k, v in classdict.items():
- if isinstance(v, BaseDescriptor):
- v.class_init(cls, k) # type:ignore[arg-type]
- for _, v in getmembers(cls):
- if isinstance(v, BaseDescriptor):
- v.subclass_init(cls) # type:ignore[arg-type]
- cls._descriptors.append(v)
- class MetaHasTraits(MetaHasDescriptors):
- """A metaclass for HasTraits."""
- def setup_class(cls: MetaHasTraits, classdict: dict[str, t.Any]) -> None:
- # for only the current class
- cls._trait_default_generators: dict[str, t.Any] = {}
- # also looking at base classes
- cls._all_trait_default_generators = {}
- cls._traits = {}
- cls._static_immutable_initial_values = {}
- super().setup_class(classdict)
- mro = cls.mro()
- for name in dir(cls):
- # Some descriptors raise AttributeError like zope.interface's
- # __provides__ attributes even though they exist. This causes
- # AttributeErrors even though they are listed in dir(cls).
- try:
- value = getattr(cls, name)
- except AttributeError:
- continue
- if isinstance(value, TraitType):
- cls._traits[name] = value
- trait = value
- default_method_name = "_%s_default" % name
- mro_trait = mro
- try:
- mro_trait = mro[: mro.index(trait.this_class) + 1] # type:ignore[arg-type]
- except ValueError:
- # this_class not in mro
- pass
- for c in mro_trait:
- if default_method_name in c.__dict__:
- cls._all_trait_default_generators[name] = c.__dict__[default_method_name]
- break
- if name in c.__dict__.get("_trait_default_generators", {}):
- cls._all_trait_default_generators[name] = c._trait_default_generators[name] # type: ignore[attr-defined]
- break
- else:
- # We don't have a dynamic default generator using @default etc.
- # Now if the default value is not dynamic and immutable (string, number)
- # and does not require any validation, we keep them in a dict
- # of initial values to speed up instance creation.
- # This is a very specific optimization, but a very common scenario in
- # for instance ipywidgets.
- none_ok = trait.default_value is None and trait.allow_none
- if (
- type(trait) in [CInt, Int]
- and trait.min is None # type: ignore[attr-defined]
- and trait.max is None # type: ignore[attr-defined]
- and (isinstance(trait.default_value, int) or none_ok)
- ):
- cls._static_immutable_initial_values[name] = trait.default_value
- elif (
- type(trait) in [CFloat, Float]
- and trait.min is None # type: ignore[attr-defined]
- and trait.max is None # type: ignore[attr-defined]
- and (isinstance(trait.default_value, float) or none_ok)
- ):
- cls._static_immutable_initial_values[name] = trait.default_value
- elif type(trait) in [CBool, Bool] and (
- isinstance(trait.default_value, bool) or none_ok
- ):
- cls._static_immutable_initial_values[name] = trait.default_value
- elif type(trait) in [CUnicode, Unicode] and (
- isinstance(trait.default_value, str) or none_ok
- ):
- cls._static_immutable_initial_values[name] = trait.default_value
- elif type(trait) == Any and (
- isinstance(trait.default_value, (str, int, float, bool)) or none_ok
- ):
- cls._static_immutable_initial_values[name] = trait.default_value
- elif type(trait) == Union and trait.default_value is None:
- cls._static_immutable_initial_values[name] = None
- elif (
- isinstance(trait, Instance)
- and trait.default_args is None
- and trait.default_kwargs is None
- and trait.allow_none
- ):
- cls._static_immutable_initial_values[name] = None
- # we always add it, because a class may change when we call add_trait
- # and then the instance may not have all the _static_immutable_initial_values
- cls._all_trait_default_generators[name] = trait.default
- def observe(*names: Sentinel | str, type: str = "change") -> ObserveHandler:
- """A decorator which can be used to observe Traits on a class.
- The handler passed to the decorator will be called with one ``change``
- dict argument. The change dictionary at least holds a 'type' key and a
- 'name' key, corresponding respectively to the type of notification and the
- name of the attribute that triggered the notification.
- Other keys may be passed depending on the value of 'type'. In the case
- where type is 'change', we also have the following keys:
- * ``owner`` : the HasTraits instance
- * ``old`` : the old value of the modified trait attribute
- * ``new`` : the new value of the modified trait attribute
- * ``name`` : the name of the modified trait attribute.
- Parameters
- ----------
- *names
- The str names of the Traits to observe on the object.
- type : str, kwarg-only
- The type of event to observe (e.g. 'change')
- """
- if not names:
- raise TypeError("Please specify at least one trait name to observe.")
- for name in names:
- if name is not All and not isinstance(name, str):
- raise TypeError("trait names to observe must be strings or All, not %r" % name)
- return ObserveHandler(names, type=type)
- def observe_compat(func: FuncT) -> FuncT:
- """Backward-compatibility shim decorator for observers
- Use with:
- @observe('name')
- @observe_compat
- def _foo_changed(self, change):
- ...
- With this, `super()._foo_changed(self, name, old, new)` in subclasses will still work.
- Allows adoption of new observer API without breaking subclasses that override and super.
- """
- def compatible_observer(
- self: t.Any, change_or_name: str, old: t.Any = Undefined, new: t.Any = Undefined
- ) -> t.Any:
- if isinstance(change_or_name, dict): # type:ignore[unreachable]
- change = Bunch(change_or_name) # type:ignore[unreachable]
- else:
- clsname = self.__class__.__name__
- warn(
- f"A parent of {clsname}._{change_or_name}_changed has adopted the new (traitlets 4.1) @observe(change) API",
- DeprecationWarning,
- stacklevel=2,
- )
- change = Bunch(
- type="change",
- old=old,
- new=new,
- name=change_or_name,
- owner=self,
- )
- return func(self, change)
- return t.cast(FuncT, compatible_observer)
- def validate(*names: Sentinel | str) -> ValidateHandler:
- """A decorator to register cross validator of HasTraits object's state
- when a Trait is set.
- The handler passed to the decorator must have one ``proposal`` dict argument.
- The proposal dictionary must hold the following keys:
- * ``owner`` : the HasTraits instance
- * ``value`` : the proposed value for the modified trait attribute
- * ``trait`` : the TraitType instance associated with the attribute
- Parameters
- ----------
- *names
- The str names of the Traits to validate.
- Notes
- -----
- Since the owner has access to the ``HasTraits`` instance via the 'owner' key,
- the registered cross validator could potentially make changes to attributes
- of the ``HasTraits`` instance. However, we recommend not to do so. The reason
- is that the cross-validation of attributes may run in arbitrary order when
- exiting the ``hold_trait_notifications`` context, and such changes may not
- commute.
- """
- if not names:
- raise TypeError("Please specify at least one trait name to validate.")
- for name in names:
- if name is not All and not isinstance(name, str):
- raise TypeError("trait names to validate must be strings or All, not %r" % name)
- return ValidateHandler(names)
- def default(name: str) -> DefaultHandler:
- """A decorator which assigns a dynamic default for a Trait on a HasTraits object.
- Parameters
- ----------
- name
- The str name of the Trait on the object whose default should be generated.
- Notes
- -----
- Unlike observers and validators which are properties of the HasTraits
- instance, default value generators are class-level properties.
- Besides, default generators are only invoked if they are registered in
- subclasses of `this_type`.
- ::
- class A(HasTraits):
- bar = Int()
- @default('bar')
- def get_bar_default(self):
- return 11
- class B(A):
- bar = Float() # This trait ignores the default generator defined in
- # the base class A
- class C(B):
- @default('bar')
- def some_other_default(self): # This default generator should not be
- return 3.0 # ignored since it is defined in a
- # class derived from B.a.this_class.
- """
- if not isinstance(name, str):
- raise TypeError("Trait name must be a string or All, not %r" % name)
- return DefaultHandler(name)
- FuncT = t.TypeVar("FuncT", bound=t.Callable[..., t.Any])
- class EventHandler(BaseDescriptor):
- def _init_call(self, func: FuncT) -> EventHandler:
- self.func = func
- return self
- @t.overload
- def __call__(self, func: FuncT, *args: t.Any, **kwargs: t.Any) -> FuncT:
- ...
- @t.overload
- def __call__(self, *args: t.Any, **kwargs: t.Any) -> t.Any:
- ...
- def __call__(self, *args: t.Any, **kwargs: t.Any) -> t.Any:
- """Pass `*args` and `**kwargs` to the handler's function if it exists."""
- if hasattr(self, "func"):
- return self.func(*args, **kwargs)
- else:
- return self._init_call(*args, **kwargs)
- def __get__(self, inst: t.Any, cls: t.Any = None) -> types.MethodType | EventHandler:
- if inst is None:
- return self
- return types.MethodType(self.func, inst)
- class ObserveHandler(EventHandler):
- def __init__(self, names: tuple[Sentinel | str, ...], type: str = "") -> None:
- self.trait_names = names
- self.type = type
- def instance_init(self, inst: HasTraits) -> None:
- inst.observe(self, self.trait_names, type=self.type)
- class ValidateHandler(EventHandler):
- def __init__(self, names: tuple[Sentinel | str, ...]) -> None:
- self.trait_names = names
- def instance_init(self, inst: HasTraits) -> None:
- inst._register_validator(self, self.trait_names)
- class DefaultHandler(EventHandler):
- def __init__(self, name: str) -> None:
- self.trait_name = name
- def class_init(self, cls: type[HasTraits], name: str | None) -> None:
- super().class_init(cls, name)
- cls._trait_default_generators[self.trait_name] = self
- class HasDescriptors(metaclass=MetaHasDescriptors):
- """The base class for all classes that have descriptors."""
- def __new__(*args: t.Any, **kwargs: t.Any) -> t.Any:
- # Pass cls as args[0] to allow "cls" as keyword argument
- cls = args[0]
- args = args[1:]
- # This is needed because object.__new__ only accepts
- # the cls argument.
- new_meth = super(HasDescriptors, cls).__new__
- if new_meth is object.__new__:
- inst = new_meth(cls)
- else:
- inst = new_meth(cls, *args, **kwargs)
- inst.setup_instance(*args, **kwargs)
- return inst
- def setup_instance(*args: t.Any, **kwargs: t.Any) -> None:
- """
- This is called **before** self.__init__ is called.
- """
- # Pass self as args[0] to allow "self" as keyword argument
- self = args[0]
- args = args[1:]
- self._cross_validation_lock = False
- cls = self.__class__
- # Let descriptors performance initialization when a HasDescriptor
- # instance is created. This allows registration of observers and
- # default creations or other bookkeepings.
- # Note that descriptors can opt-out of this behavior by overriding
- # subclass_init.
- for init in cls._instance_inits:
- init(self)
- class HasTraits(HasDescriptors, metaclass=MetaHasTraits):
- _trait_values: dict[str, t.Any]
- _static_immutable_initial_values: dict[str, t.Any]
- _trait_notifiers: dict[str | Sentinel, t.Any]
- _trait_validators: dict[str | Sentinel, t.Any]
- _cross_validation_lock: bool
- _traits: dict[str, t.Any]
- _all_trait_default_generators: dict[str, t.Any]
- def setup_instance(*args: t.Any, **kwargs: t.Any) -> None:
- # Pass self as args[0] to allow "self" as keyword argument
- self = args[0]
- args = args[1:]
- # although we'd prefer to set only the initial values not present
- # in kwargs, we will overwrite them in `__init__`, and simply making
- # a copy of a dict is faster than checking for each key.
- self._trait_values = self._static_immutable_initial_values.copy()
- self._trait_notifiers = {}
- self._trait_validators = {}
- self._cross_validation_lock = False
- super(HasTraits, self).setup_instance(*args, **kwargs)
- def __init__(self, *args: t.Any, **kwargs: t.Any) -> None:
- # Allow trait values to be set using keyword arguments.
- # We need to use setattr for this to trigger validation and
- # notifications.
- super_args = args
- super_kwargs = {}
- if kwargs:
- # this is a simplified (and faster) version of
- # the hold_trait_notifications(self) context manager
- def ignore(change: Bunch) -> None:
- pass
- self.notify_change = ignore # type:ignore[method-assign]
- self._cross_validation_lock = True
- changes = {}
- for key, value in kwargs.items():
- if self.has_trait(key):
- setattr(self, key, value)
- changes[key] = Bunch(
- name=key,
- old=None,
- new=value,
- owner=self,
- type="change",
- )
- else:
- # passthrough args that don't set traits to super
- super_kwargs[key] = value
- # notify and cross validate all trait changes that were set in kwargs
- changed = set(kwargs) & set(self._traits)
- for key in changed:
- value = self._traits[key]._cross_validate(self, getattr(self, key))
- self.set_trait(key, value)
- changes[key]["new"] = value
- self._cross_validation_lock = False
- # Restore method retrieval from class
- del self.notify_change
- for key in changed:
- self.notify_change(changes[key])
- try:
- super().__init__(*super_args, **super_kwargs)
- except TypeError as e:
- arg_s_list = [repr(arg) for arg in super_args]
- for k, v in super_kwargs.items():
- arg_s_list.append(f"{k}={v!r}")
- arg_s = ", ".join(arg_s_list)
- warn(
- "Passing unrecognized arguments to super({classname}).__init__({arg_s}).\n"
- "{error}\n"
- "This is deprecated in traitlets 4.2."
- "This error will be raised in a future release of traitlets.".format(
- arg_s=arg_s,
- classname=self.__class__.__name__,
- error=e,
- ),
- DeprecationWarning,
- stacklevel=2,
- )
- def __getstate__(self) -> dict[str, t.Any]:
- d = self.__dict__.copy()
- # event handlers stored on an instance are
- # expected to be reinstantiated during a
- # recall of instance_init during __setstate__
- d["_trait_notifiers"] = {}
- d["_trait_validators"] = {}
- d["_trait_values"] = self._trait_values.copy()
- d["_cross_validation_lock"] = False # FIXME: raise if cloning locked!
- return d
- def __setstate__(self, state: dict[str, t.Any]) -> None:
- self.__dict__ = state.copy()
- # event handlers are reassigned to self
- cls = self.__class__
- for key in dir(cls):
- # Some descriptors raise AttributeError like zope.interface's
- # __provides__ attributes even though they exist. This causes
- # AttributeErrors even though they are listed in dir(cls).
- try:
- value = getattr(cls, key)
- except AttributeError:
- pass
- else:
- if isinstance(value, EventHandler):
- value.instance_init(self)
- @property
- @contextlib.contextmanager
- def cross_validation_lock(self) -> t.Any:
- """
- A contextmanager for running a block with our cross validation lock set
- to True.
- At the end of the block, the lock's value is restored to its value
- prior to entering the block.
- """
- if self._cross_validation_lock:
- yield
- return
- else:
- try:
- self._cross_validation_lock = True
- yield
- finally:
- self._cross_validation_lock = False
- @contextlib.contextmanager
- def hold_trait_notifications(self) -> t.Any:
- """Context manager for bundling trait change notifications and cross
- validation.
- Use this when doing multiple trait assignments (init, config), to avoid
- race conditions in trait notifiers requesting other trait values.
- All trait notifications will fire after all values have been assigned.
- """
- if self._cross_validation_lock:
- yield
- return
- else:
- cache: dict[str, list[Bunch]] = {}
- def compress(past_changes: list[Bunch] | None, change: Bunch) -> list[Bunch]:
- """Merges the provided change with the last if possible."""
- if past_changes is None:
- return [change]
- else:
- if past_changes[-1]["type"] == "change" and change.type == "change":
- past_changes[-1]["new"] = change.new
- else:
- # In case of changes other than 'change', append the notification.
- past_changes.append(change)
- return past_changes
- def hold(change: Bunch) -> None:
- name = change.name
- cache[name] = compress(cache.get(name), change)
- try:
- # Replace notify_change with `hold`, caching and compressing
- # notifications, disable cross validation and yield.
- self.notify_change = hold # type:ignore[method-assign]
- self._cross_validation_lock = True
- yield
- # Cross validate final values when context is released.
- for name in list(cache.keys()):
- trait = getattr(self.__class__, name)
- value = trait._cross_validate(self, getattr(self, name))
- self.set_trait(name, value)
- except TraitError as e:
- # Roll back in case of TraitError during final cross validation.
- self.notify_change = lambda x: None # type:ignore[method-assign, assignment] # noqa: ARG005
- for name, changes in cache.items():
- for change in changes[::-1]:
- # TODO: Separate in a rollback function per notification type.
- if change.type == "change":
- if change.old is not Undefined:
- self.set_trait(name, change.old)
- else:
- self._trait_values.pop(name)
- cache = {}
- raise e
- finally:
- self._cross_validation_lock = False
- # Restore method retrieval from class
- del self.notify_change
- # trigger delayed notifications
- for changes in cache.values():
- for change in changes:
- self.notify_change(change)
- def _notify_trait(self, name: str, old_value: t.Any, new_value: t.Any) -> None:
- self.notify_change(
- Bunch(
- name=name,
- old=old_value,
- new=new_value,
- owner=self,
- type="change",
- )
- )
- def notify_change(self, change: Bunch) -> None:
- """Notify observers of a change event"""
- return self._notify_observers(change)
- def _notify_observers(self, event: Bunch) -> None:
- """Notify observers of any event"""
- if not isinstance(event, Bunch):
- # cast to bunch if given a dict
- event = Bunch(event) # type:ignore[unreachable]
- name, type = event["name"], event["type"]
- callables = []
- if name in self._trait_notifiers:
- callables.extend(self._trait_notifiers.get(name, {}).get(type, []))
- callables.extend(self._trait_notifiers.get(name, {}).get(All, []))
- if All in self._trait_notifiers:
- callables.extend(self._trait_notifiers.get(All, {}).get(type, []))
- callables.extend(self._trait_notifiers.get(All, {}).get(All, []))
- # Now static ones
- magic_name = "_%s_changed" % name
- if event["type"] == "change" and hasattr(self, magic_name):
- class_value = getattr(self.__class__, magic_name)
- if not isinstance(class_value, ObserveHandler):
- deprecated_method(
- class_value,
- self.__class__,
- magic_name,
- "use @observe and @unobserve instead.",
- )
- cb = getattr(self, magic_name)
- # Only append the magic method if it was not manually registered
- if cb not in callables:
- callables.append(_callback_wrapper(cb))
- # Call them all now
- # Traits catches and logs errors here. I allow them to raise
- for c in callables:
- # Bound methods have an additional 'self' argument.
- if isinstance(c, _CallbackWrapper):
- c = c.__call__
- elif isinstance(c, EventHandler) and c.name is not None:
- c = getattr(self, c.name)
- c(event)
- def _add_notifiers(
- self, handler: t.Callable[..., t.Any], name: Sentinel | str, type: str | Sentinel
- ) -> None:
- if name not in self._trait_notifiers:
- nlist: list[t.Any] = []
- self._trait_notifiers[name] = {type: nlist}
- else:
- if type not in self._trait_notifiers[name]:
- nlist = []
- self._trait_notifiers[name][type] = nlist
- else:
- nlist = self._trait_notifiers[name][type]
- if handler not in nlist:
- nlist.append(handler)
- def _remove_notifiers(
- self, handler: t.Callable[..., t.Any] | None, name: Sentinel | str, type: str | Sentinel
- ) -> None:
- try:
- if handler is None:
- del self._trait_notifiers[name][type]
- else:
- self._trait_notifiers[name][type].remove(handler)
- except KeyError:
- pass
- def on_trait_change(
- self,
- handler: EventHandler | None = None,
- name: Sentinel | str | None = None,
- remove: bool = False,
- ) -> None:
- """DEPRECATED: Setup a handler to be called when a trait changes.
- This is used to setup dynamic notifications of trait changes.
- Static handlers can be created by creating methods on a HasTraits
- subclass with the naming convention '_[traitname]_changed'. Thus,
- to create static handler for the trait 'a', create the method
- _a_changed(self, name, old, new) (fewer arguments can be used, see
- below).
- If `remove` is True and `handler` is not specified, all change
- handlers for the specified name are uninstalled.
- Parameters
- ----------
- handler : callable, None
- A callable that is called when a trait changes. Its
- signature can be handler(), handler(name), handler(name, new),
- handler(name, old, new), or handler(name, old, new, self).
- name : list, str, None
- If None, the handler will apply to all traits. If a list
- of str, handler will apply to all names in the list. If a
- str, the handler will apply just to that name.
- remove : bool
- If False (the default), then install the handler. If True
- then unintall it.
- """
- warn(
- "on_trait_change is deprecated in traitlets 4.1: use observe instead",
- DeprecationWarning,
- stacklevel=2,
- )
- if name is None:
- name = All
- if remove:
- self.unobserve(_callback_wrapper(handler), names=name)
- else:
- self.observe(_callback_wrapper(handler), names=name)
- def observe(
- self,
- handler: t.Callable[..., t.Any],
- names: Sentinel | str | t.Iterable[Sentinel | str] = All,
- type: Sentinel | str = "change",
- ) -> None:
- """Setup a handler to be called when a trait changes.
- This is used to setup dynamic notifications of trait changes.
- Parameters
- ----------
- handler : callable
- A callable that is called when a trait changes. Its
- signature should be ``handler(change)``, where ``change`` is a
- dictionary. The change dictionary at least holds a 'type' key.
- * ``type``: the type of notification.
- Other keys may be passed depending on the value of 'type'. In the
- case where type is 'change', we also have the following keys:
- * ``owner`` : the HasTraits instance
- * ``old`` : the old value of the modified trait attribute
- * ``new`` : the new value of the modified trait attribute
- * ``name`` : the name of the modified trait attribute.
- names : list, str, All
- If names is All, the handler will apply to all traits. If a list
- of str, handler will apply to all names in the list. If a
- str, the handler will apply just to that name.
- type : str, All (default: 'change')
- The type of notification to filter by. If equal to All, then all
- notifications are passed to the observe handler.
- """
- for name in parse_notifier_name(names):
- self._add_notifiers(handler, name, type)
- def unobserve(
- self,
- handler: t.Callable[..., t.Any],
- names: Sentinel | str | t.Iterable[Sentinel | str] = All,
- type: Sentinel | str = "change",
- ) -> None:
- """Remove a trait change handler.
- This is used to unregister handlers to trait change notifications.
- Parameters
- ----------
- handler : callable
- The callable called when a trait attribute changes.
- names : list, str, All (default: All)
- The names of the traits for which the specified handler should be
- uninstalled. If names is All, the specified handler is uninstalled
- from the list of notifiers corresponding to all changes.
- type : str or All (default: 'change')
- The type of notification to filter by. If All, the specified handler
- is uninstalled from the list of notifiers corresponding to all types.
- """
- for name in parse_notifier_name(names):
- self._remove_notifiers(handler, name, type)
- def unobserve_all(self, name: str | t.Any = All) -> None:
- """Remove trait change handlers of any type for the specified name.
- If name is not specified, removes all trait notifiers."""
- if name is All:
- self._trait_notifiers = {}
- else:
- try:
- del self._trait_notifiers[name]
- except KeyError:
- pass
- def _register_validator(
- self, handler: t.Callable[..., None], names: tuple[str | Sentinel, ...]
- ) -> None:
- """Setup a handler to be called when a trait should be cross validated.
- This is used to setup dynamic notifications for cross-validation.
- If a validator is already registered for any of the provided names, a
- TraitError is raised and no new validator is registered.
- Parameters
- ----------
- handler : callable
- A callable that is called when the given trait is cross-validated.
- Its signature is handler(proposal), where proposal is a Bunch (dictionary with attribute access)
- with the following attributes/keys:
- * ``owner`` : the HasTraits instance
- * ``value`` : the proposed value for the modified trait attribute
- * ``trait`` : the TraitType instance associated with the attribute
- names : List of strings
- The names of the traits that should be cross-validated
- """
- for name in names:
- magic_name = "_%s_validate" % name
- if hasattr(self, magic_name):
- class_value = getattr(self.__class__, magic_name)
- if not isinstance(class_value, ValidateHandler):
- deprecated_method(
- class_value,
- self.__class__,
- magic_name,
- "use @validate decorator instead.",
- )
- for name in names:
- self._trait_validators[name] = handler
- def add_traits(self, **traits: t.Any) -> None:
- """Dynamically add trait attributes to the HasTraits instance."""
- cls = self.__class__
- attrs = {"__module__": cls.__module__}
- if hasattr(cls, "__qualname__"):
- # __qualname__ introduced in Python 3.3 (see PEP 3155)
- attrs["__qualname__"] = cls.__qualname__
- attrs.update(traits)
- self.__class__ = type(cls.__name__, (cls,), attrs)
- for trait in traits.values():
- trait.instance_init(self)
- def set_trait(self, name: str, value: t.Any) -> None:
- """Forcibly sets trait attribute, including read-only attributes."""
- cls = self.__class__
- if not self.has_trait(name):
- raise TraitError(f"Class {cls.__name__} does not have a trait named {name}")
- getattr(cls, name).set(self, value)
- @classmethod
- def class_trait_names(cls: type[HasTraits], **metadata: t.Any) -> list[str]:
- """Get a list of all the names of this class' traits.
- This method is just like the :meth:`trait_names` method,
- but is unbound.
- """
- return list(cls.class_traits(**metadata))
- @classmethod
- def class_traits(cls: type[HasTraits], **metadata: t.Any) -> dict[str, TraitType[t.Any, t.Any]]:
- """Get a ``dict`` of all the traits of this class. The dictionary
- is keyed on the name and the values are the TraitType objects.
- This method is just like the :meth:`traits` method, but is unbound.
- The TraitTypes returned don't know anything about the values
- that the various HasTrait's instances are holding.
- The metadata kwargs allow functions to be passed in which
- filter traits based on metadata values. The functions should
- take a single value as an argument and return a boolean. If
- any function returns False, then the trait is not included in
- the output. If a metadata key doesn't exist, None will be passed
- to the function.
- """
- traits = cls._traits.copy()
- if len(metadata) == 0:
- return traits
- result = {}
- for name, trait in traits.items():
- for meta_name, meta_eval in metadata.items():
- if not callable(meta_eval):
- meta_eval = _SimpleTest(meta_eval)
- if not meta_eval(trait.metadata.get(meta_name, None)):
- break
- else:
- result[name] = trait
- return result
- @classmethod
- def class_own_traits(
- cls: type[HasTraits], **metadata: t.Any
- ) -> dict[str, TraitType[t.Any, t.Any]]:
- """Get a dict of all the traitlets defined on this class, not a parent.
- Works like `class_traits`, except for excluding traits from parents.
- """
- sup = super(cls, cls)
- return {
- n: t
- for (n, t) in cls.class_traits(**metadata).items()
- if getattr(sup, n, None) is not t
- }
- def has_trait(self, name: str) -> bool:
- """Returns True if the object has a trait with the specified name."""
- return name in self._traits
- def trait_has_value(self, name: str) -> bool:
- """Returns True if the specified trait has a value.
- This will return false even if ``getattr`` would return a
- dynamically generated default value. These default values
- will be recognized as existing only after they have been
- generated.
- Example
- .. code-block:: python
- class MyClass(HasTraits):
- i = Int()
- mc = MyClass()
- assert not mc.trait_has_value("i")
- mc.i # generates a default value
- assert mc.trait_has_value("i")
- """
- return name in self._trait_values
- def trait_values(self, **metadata: t.Any) -> dict[str, t.Any]:
- """A ``dict`` of trait names and their values.
- The metadata kwargs allow functions to be passed in which
- filter traits based on metadata values. The functions should
- take a single value as an argument and return a boolean. If
- any function returns False, then the trait is not included in
- the output. If a metadata key doesn't exist, None will be passed
- to the function.
- Returns
- -------
- A ``dict`` of trait names and their values.
- Notes
- -----
- Trait values are retrieved via ``getattr``, any exceptions raised
- by traits or the operations they may trigger will result in the
- absence of a trait value in the result ``dict``.
- """
- return {name: getattr(self, name) for name in self.trait_names(**metadata)}
- def _get_trait_default_generator(self, name: str) -> t.Any:
- """Return default generator for a given trait
- Walk the MRO to resolve the correct default generator according to inheritance.
- """
- method_name = "_%s_default" % name
- if method_name in self.__dict__:
- return getattr(self, method_name)
- if method_name in self.__class__.__dict__:
- return getattr(self.__class__, method_name)
- return self._all_trait_default_generators[name]
- def trait_defaults(self, *names: str, **metadata: t.Any) -> dict[str, t.Any] | Sentinel:
- """Return a trait's default value or a dictionary of them
- Notes
- -----
- Dynamically generated default values may
- depend on the current state of the object."""
- for n in names:
- if not self.has_trait(n):
- raise TraitError(f"'{n}' is not a trait of '{type(self).__name__}' instances")
- if len(names) == 1 and len(metadata) == 0:
- return t.cast(Sentinel, self._get_trait_default_generator(names[0])(self))
- trait_names = self.trait_names(**metadata)
- trait_names.extend(names)
- defaults = {}
- for n in trait_names:
- defaults[n] = self._get_trait_default_generator(n)(self)
- return defaults
- def trait_names(self, **metadata: t.Any) -> list[str]:
- """Get a list of all the names of this class' traits."""
- return list(self.traits(**metadata))
- def traits(self, **metadata: t.Any) -> dict[str, TraitType[t.Any, t.Any]]:
- """Get a ``dict`` of all the traits of this class. The dictionary
- is keyed on the name and the values are the TraitType objects.
- The TraitTypes returned don't know anything about the values
- that the various HasTrait's instances are holding.
- The metadata kwargs allow functions to be passed in which
- filter traits based on metadata values. The functions should
- take a single value as an argument and return a boolean. If
- any function returns False, then the trait is not included in
- the output. If a metadata key doesn't exist, None will be passed
- to the function.
- """
- traits = self._traits.copy()
- if len(metadata) == 0:
- return traits
- result = {}
- for name, trait in traits.items():
- for meta_name, meta_eval in metadata.items():
- if not callable(meta_eval):
- meta_eval = _SimpleTest(meta_eval)
- if not meta_eval(trait.metadata.get(meta_name, None)):
- break
- else:
- result[name] = trait
- return result
- def trait_metadata(self, traitname: str, key: str, default: t.Any = None) -> t.Any:
- """Get metadata values for trait by key."""
- try:
- trait = getattr(self.__class__, traitname)
- except AttributeError as e:
- raise TraitError(
- f"Class {self.__class__.__name__} does not have a trait named {traitname}"
- ) from e
- metadata_name = "_" + traitname + "_metadata"
- if hasattr(self, metadata_name) and key in getattr(self, metadata_name):
- return getattr(self, metadata_name).get(key, default)
- else:
- return trait.metadata.get(key, default)
- @classmethod
- def class_own_trait_events(cls: type[HasTraits], name: str) -> dict[str, EventHandler]:
- """Get a dict of all event handlers defined on this class, not a parent.
- Works like ``event_handlers``, except for excluding traits from parents.
- """
- sup = super(cls, cls)
- return {
- n: e
- for (n, e) in cls.events(name).items() # type:ignore[attr-defined]
- if getattr(sup, n, None) is not e
- }
- @classmethod
- def trait_events(cls: type[HasTraits], name: str | None = None) -> dict[str, EventHandler]:
- """Get a ``dict`` of all the event handlers of this class.
- Parameters
- ----------
- name : str (default: None)
- The name of a trait of this class. If name is ``None`` then all
- the event handlers of this class will be returned instead.
- Returns
- -------
- The event handlers associated with a trait name, or all event handlers.
- """
- events = {}
- for k, v in getmembers(cls):
- if isinstance(v, EventHandler):
- if name is None:
- events[k] = v
- elif name in v.trait_names: # type:ignore[attr-defined]
- events[k] = v
- elif hasattr(v, "tags"):
- if cls.trait_names(**v.tags):
- events[k] = v
- return events
- # -----------------------------------------------------------------------------
- # Actual TraitTypes implementations/subclasses
- # -----------------------------------------------------------------------------
- # -----------------------------------------------------------------------------
- # TraitTypes subclasses for handling classes and instances of classes
- # -----------------------------------------------------------------------------
- class ClassBasedTraitType(TraitType[G, S]):
- """
- A trait with error reporting and string -> type resolution for Type,
- Instance and This.
- """
- def _resolve_string(self, string: str) -> t.Any:
- """
- Resolve a string supplied for a type into an actual object.
- """
- return import_item(string)
- class Type(ClassBasedTraitType[G, S]):
- """A trait whose value must be a subclass of a specified class."""
- if t.TYPE_CHECKING:
- @t.overload
- def __init__(
- self: Type[type, type],
- default_value: Sentinel | None | str = ...,
- klass: None | str = ...,
- allow_none: Literal[False] = ...,
- read_only: bool | None = ...,
- help: str | None = ...,
- config: t.Any | None = ...,
- **kwargs: t.Any,
- ) -> None:
- ...
- @t.overload
- def __init__(
- self: Type[type | None, type | None],
- default_value: Sentinel | None | str = ...,
- klass: None | str = ...,
- allow_none: Literal[True] = ...,
- read_only: bool | None = ...,
- help: str | None = ...,
- config: t.Any | None = ...,
- **kwargs: t.Any,
- ) -> None:
- ...
- @t.overload
- def __init__(
- self: Type[S, S],
- default_value: S = ...,
- klass: S = ...,
- allow_none: Literal[False] = ...,
- read_only: bool | None = ...,
- help: str | None = ...,
- config: t.Any | None = ...,
- **kwargs: t.Any,
- ) -> None:
- ...
- @t.overload
- def __init__(
- self: Type[S | None, S | None],
- default_value: S | None = ...,
- klass: S = ...,
- allow_none: Literal[True] = ...,
- read_only: bool | None = ...,
- help: str | None = ...,
- config: t.Any | None = ...,
- **kwargs: t.Any,
- ) -> None:
- ...
- def __init__(
- self,
- default_value: t.Any = Undefined,
- klass: t.Any = None,
- allow_none: bool = False,
- read_only: bool | None = None,
- help: str | None = None,
- config: t.Any | None = None,
- **kwargs: t.Any,
- ) -> None:
- """Construct a Type trait
- A Type trait specifies that its values must be subclasses of
- a particular class.
- If only ``default_value`` is given, it is used for the ``klass`` as
- well. If neither are given, both default to ``object``.
- Parameters
- ----------
- default_value : class, str or None
- The default value must be a subclass of klass. If an str,
- the str must be a fully specified class name, like 'foo.bar.Bah'.
- The string is resolved into real class, when the parent
- :class:`HasTraits` class is instantiated.
- klass : class, str [ default object ]
- Values of this trait must be a subclass of klass. The klass
- may be specified in a string like: 'foo.bar.MyClass'.
- The string is resolved into real class, when the parent
- :class:`HasTraits` class is instantiated.
- allow_none : bool [ default False ]
- Indicates whether None is allowed as an assignable value.
- **kwargs
- extra kwargs passed to `ClassBasedTraitType`
- """
- if default_value is Undefined:
- new_default_value = object if (klass is None) else klass
- else:
- new_default_value = default_value
- if klass is None:
- if (default_value is None) or (default_value is Undefined):
- klass = object
- else:
- klass = default_value
- if not (inspect.isclass(klass) or isinstance(klass, str)):
- raise TraitError("A Type trait must specify a class.")
- self.klass = klass
- super().__init__(
- new_default_value,
- allow_none=allow_none,
- read_only=read_only,
- help=help,
- config=config,
- **kwargs,
- )
- def validate(self, obj: t.Any, value: t.Any) -> G:
- """Validates that the value is a valid object instance."""
- if isinstance(value, str):
- try:
- value = self._resolve_string(value)
- except ImportError as e:
- raise TraitError(
- f"The '{self.name}' trait of {obj} instance must be a type, but "
- f"{value!r} could not be imported"
- ) from e
- try:
- if issubclass(value, self.klass): # type:ignore[arg-type]
- return t.cast(G, value)
- except Exception:
- pass
- self.error(obj, value)
- def info(self) -> str:
- """Returns a description of the trait."""
- if isinstance(self.klass, str):
- klass = self.klass
- else:
- klass = self.klass.__module__ + "." + self.klass.__name__
- result = "a subclass of '%s'" % klass
- if self.allow_none:
- return result + " or None"
- return result
- def instance_init(self, obj: t.Any) -> None:
- # we can't do this in subclass_init because that
- # might be called before all imports are done.
- self._resolve_classes()
- def _resolve_classes(self) -> None:
- if isinstance(self.klass, str):
- self.klass = self._resolve_string(self.klass)
- if isinstance(self.default_value, str):
- self.default_value = self._resolve_string(self.default_value)
- def default_value_repr(self) -> str:
- value = self.default_value
- assert value is not None
- if isinstance(value, str):
- return repr(value)
- else:
- return repr(f"{value.__module__}.{value.__name__}")
- class Instance(ClassBasedTraitType[T, T]):
- """A trait whose value must be an instance of a specified class.
- The value can also be an instance of a subclass of the specified class.
- Subclasses can declare default classes by overriding the klass attribute
- """
- klass: str | type[T] | None = None
- if t.TYPE_CHECKING:
- @t.overload
- def __init__(
- self: Instance[T],
- klass: type[T] = ...,
- args: tuple[t.Any, ...] | None = ...,
- kw: dict[str, t.Any] | None = ...,
- allow_none: Literal[False] = ...,
- read_only: bool | None = ...,
- help: str | None = ...,
- **kwargs: t.Any,
- ) -> None:
- ...
- @t.overload
- def __init__(
- self: Instance[T | None],
- klass: type[T] = ...,
- args: tuple[t.Any, ...] | None = ...,
- kw: dict[str, t.Any] | None = ...,
- allow_none: Literal[True] = ...,
- read_only: bool | None = ...,
- help: str | None = ...,
- **kwargs: t.Any,
- ) -> None:
- ...
- @t.overload
- def __init__(
- self: Instance[t.Any],
- klass: str | None = ...,
- args: tuple[t.Any, ...] | None = ...,
- kw: dict[str, t.Any] | None = ...,
- allow_none: Literal[False] = ...,
- read_only: bool | None = ...,
- help: str | None = ...,
- **kwargs: t.Any,
- ) -> None:
- ...
- @t.overload
- def __init__(
- self: Instance[t.Any | None],
- klass: str | None = ...,
- args: tuple[t.Any, ...] | None = ...,
- kw: dict[str, t.Any] | None = ...,
- allow_none: Literal[True] = ...,
- read_only: bool | None = ...,
- help: str | None = ...,
- **kwargs: t.Any,
- ) -> None:
- ...
- def __init__(
- self,
- klass: str | type[T] | None = None,
- args: tuple[t.Any, ...] | None = None,
- kw: dict[str, t.Any] | None = None,
- allow_none: bool = False,
- read_only: bool | None = None,
- help: str | None = None,
- **kwargs: t.Any,
- ) -> None:
- """Construct an Instance trait.
- This trait allows values that are instances of a particular
- class or its subclasses. Our implementation is quite different
- from that of enthough.traits as we don't allow instances to be used
- for klass and we handle the ``args`` and ``kw`` arguments differently.
- Parameters
- ----------
- klass : class, str
- The class that forms the basis for the trait. Class names
- can also be specified as strings, like 'foo.bar.Bar'.
- args : tuple
- Positional arguments for generating the default value.
- kw : dict
- Keyword arguments for generating the default value.
- allow_none : bool [ default False ]
- Indicates whether None is allowed as a value.
- **kwargs
- Extra kwargs passed to `ClassBasedTraitType`
- Notes
- -----
- If both ``args`` and ``kw`` are None, then the default value is None.
- If ``args`` is a tuple and ``kw`` is a dict, then the default is
- created as ``klass(*args, **kw)``. If exactly one of ``args`` or ``kw`` is
- None, the None is replaced by ``()`` or ``{}``, respectively.
- """
- if klass is None:
- klass = self.klass
- if (klass is not None) and (inspect.isclass(klass) or isinstance(klass, str)):
- self.klass = klass
- else:
- raise TraitError("The klass attribute must be a class not: %r" % klass)
- if (kw is not None) and not isinstance(kw, dict):
- raise TraitError("The 'kw' argument must be a dict or None.")
- if (args is not None) and not isinstance(args, tuple):
- raise TraitError("The 'args' argument must be a tuple or None.")
- self.default_args = args
- self.default_kwargs = kw
- super().__init__(allow_none=allow_none, read_only=read_only, help=help, **kwargs)
- def validate(self, obj: t.Any, value: t.Any) -> T | None:
- assert self.klass is not None
- if self.allow_none and value is None:
- return value
- if isinstance(value, self.klass): # type:ignore[arg-type]
- return t.cast(T, value)
- else:
- self.error(obj, value)
- def info(self) -> str:
- if isinstance(self.klass, str):
- result = add_article(self.klass)
- else:
- result = describe("a", self.klass)
- if self.allow_none:
- result += " or None"
- return result
- def instance_init(self, obj: t.Any) -> None:
- # we can't do this in subclass_init because that
- # might be called before all imports are done.
- self._resolve_classes()
- def _resolve_classes(self) -> None:
- if isinstance(self.klass, str):
- self.klass = self._resolve_string(self.klass)
- def make_dynamic_default(self) -> T | None:
- if (self.default_args is None) and (self.default_kwargs is None):
- return None
- assert self.klass is not None
- return self.klass(*(self.default_args or ()), **(self.default_kwargs or {})) # type:ignore[operator]
- def default_value_repr(self) -> str:
- return repr(self.make_dynamic_default())
- def from_string(self, s: str) -> T | None:
- return t.cast(T, _safe_literal_eval(s))
- class ForwardDeclaredMixin:
- """
- Mixin for forward-declared versions of Instance and Type.
- """
- def _resolve_string(self, string: str) -> t.Any:
- """
- Find the specified class name by looking for it in the module in which
- our this_class attribute was defined.
- """
- modname = self.this_class.__module__ # type:ignore[attr-defined]
- return import_item(".".join([modname, string]))
- class ForwardDeclaredType(ForwardDeclaredMixin, Type[G, S]):
- """
- Forward-declared version of Type.
- """
- class ForwardDeclaredInstance(ForwardDeclaredMixin, Instance[T]):
- """
- Forward-declared version of Instance.
- """
- class This(ClassBasedTraitType[t.Optional[T], t.Optional[T]]):
- """A trait for instances of the class containing this trait.
- Because how how and when class bodies are executed, the ``This``
- trait can only have a default value of None. This, and because we
- always validate default values, ``allow_none`` is *always* true.
- """
- info_text = "an instance of the same type as the receiver or None"
- def __init__(self, **kwargs: t.Any) -> None:
- super().__init__(None, **kwargs)
- def validate(self, obj: t.Any, value: t.Any) -> HasTraits | None:
- # What if value is a superclass of obj.__class__? This is
- # complicated if it was the superclass that defined the This
- # trait.
- assert self.this_class is not None
- if isinstance(value, self.this_class) or (value is None):
- return value
- else:
- self.error(obj, value)
- class Union(TraitType[t.Any, t.Any]):
- """A trait type representing a Union type."""
- def __init__(self, trait_types: t.Any, **kwargs: t.Any) -> None:
- """Construct a Union trait.
- This trait allows values that are allowed by at least one of the
- specified trait types. A Union traitlet cannot have metadata on
- its own, besides the metadata of the listed types.
- Parameters
- ----------
- trait_types : sequence
- The list of trait types of length at least 1.
- **kwargs
- Extra kwargs passed to `TraitType`
- Notes
- -----
- Union([Float(), Bool(), Int()]) attempts to validate the provided values
- with the validation function of Float, then Bool, and finally Int.
- Parsing from string is ambiguous for container types which accept other
- collection-like literals (e.g. List accepting both `[]` and `()`
- precludes Union from ever parsing ``Union([List(), Tuple()])`` as a tuple;
- you can modify behaviour of too permissive container traits by overriding
- ``_literal_from_string_pairs`` in subclasses.
- Similarly, parsing unions of numeric types is only unambiguous if
- types are provided in order of increasing permissiveness, e.g.
- ``Union([Int(), Float()])`` (since floats accept integer-looking values).
- """
- self.trait_types = list(trait_types)
- self.info_text = " or ".join([tt.info() for tt in self.trait_types])
- super().__init__(**kwargs)
- def default(self, obj: t.Any = None) -> t.Any:
- default = super().default(obj)
- for trait in self.trait_types:
- if default is Undefined:
- default = trait.default(obj)
- else:
- break
- return default
- def class_init(self, cls: type[HasTraits], name: str | None) -> None:
- for trait_type in reversed(self.trait_types):
- trait_type.class_init(cls, None)
- super().class_init(cls, name)
- def subclass_init(self, cls: type[t.Any]) -> None:
- for trait_type in reversed(self.trait_types):
- trait_type.subclass_init(cls)
- # explicitly not calling super().subclass_init(cls)
- # to opt out of instance_init
- def validate(self, obj: t.Any, value: t.Any) -> t.Any:
- with obj.cross_validation_lock:
- for trait_type in self.trait_types:
- try:
- v = trait_type._validate(obj, value)
- # In the case of an element trait, the name is None
- if self.name is not None:
- setattr(obj, "_" + self.name + "_metadata", trait_type.metadata)
- return v
- except TraitError:
- continue
- self.error(obj, value)
- def __or__(self, other: t.Any) -> Union:
- if isinstance(other, Union):
- return Union(self.trait_types + other.trait_types)
- else:
- return Union([*self.trait_types, other])
- def from_string(self, s: str) -> t.Any:
- for trait_type in self.trait_types:
- try:
- v = trait_type.from_string(s)
- return trait_type.validate(None, v)
- except (TraitError, ValueError):
- continue
- return super().from_string(s)
- # -----------------------------------------------------------------------------
- # Basic TraitTypes implementations/subclasses
- # -----------------------------------------------------------------------------
- class Any(TraitType[t.Optional[t.Any], t.Optional[t.Any]]):
- """A trait which allows any value."""
- if t.TYPE_CHECKING:
- @t.overload
- def __init__(
- self: Any,
- default_value: t.Any = ...,
- *,
- allow_none: Literal[False],
- read_only: bool | None = ...,
- help: str | None = ...,
- config: t.Any | None = ...,
- **kwargs: t.Any,
- ) -> None:
- ...
- @t.overload
- def __init__(
- self: Any,
- default_value: t.Any = ...,
- *,
- allow_none: Literal[True],
- read_only: bool | None = ...,
- help: str | None = ...,
- config: t.Any | None = ...,
- **kwargs: t.Any,
- ) -> None:
- ...
- @t.overload
- def __init__(
- self: Any,
- default_value: t.Any = ...,
- *,
- allow_none: Literal[True, False] = ...,
- help: str | None = ...,
- read_only: bool | None = False,
- config: t.Any = None,
- **kwargs: t.Any,
- ) -> None:
- ...
- def __init__(
- self: Any,
- default_value: t.Any = ...,
- *,
- allow_none: bool = False,
- help: str | None = "",
- read_only: bool | None = False,
- config: t.Any = None,
- **kwargs: t.Any,
- ) -> None:
- ...
- @t.overload
- def __get__(self, obj: None, cls: type[t.Any]) -> Any:
- ...
- @t.overload
- def __get__(self, obj: t.Any, cls: type[t.Any]) -> t.Any:
- ...
- def __get__(self, obj: t.Any | None, cls: type[t.Any]) -> t.Any | Any:
- ...
- default_value: t.Any | None = None
- allow_none = True
- info_text = "any value"
- def subclass_init(self, cls: type[t.Any]) -> None:
- pass # fully opt out of instance_init
- def _validate_bounds(
- trait: Int[t.Any, t.Any] | Float[t.Any, t.Any], obj: t.Any, value: t.Any
- ) -> t.Any:
- """
- Validate that a number to be applied to a trait is between bounds.
- If value is not between min_bound and max_bound, this raises a
- TraitError with an error message appropriate for this trait.
- """
- if trait.min is not None and value < trait.min:
- raise TraitError(
- f"The value of the '{trait.name}' trait of {class_of(obj)} instance should "
- f"not be less than {trait.min}, but a value of {value} was "
- "specified"
- )
- if trait.max is not None and value > trait.max:
- raise TraitError(
- f"The value of the '{trait.name}' trait of {class_of(obj)} instance should "
- f"not be greater than {trait.max}, but a value of {value} was "
- "specified"
- )
- return value
- # I = t.TypeVar('I', t.Optional[int], int)
- class Int(TraitType[G, S]):
- """An int trait."""
- default_value = 0
- info_text = "an int"
- @t.overload
- def __init__(
- self: Int[int, int],
- default_value: int | Sentinel = ...,
- allow_none: Literal[False] = ...,
- read_only: bool | None = ...,
- help: str | None = ...,
- config: t.Any | None = ...,
- **kwargs: t.Any,
- ) -> None:
- ...
- @t.overload
- def __init__(
- self: Int[int | None, int | None],
- default_value: int | Sentinel | None = ...,
- allow_none: Literal[True] = ...,
- read_only: bool | None = ...,
- help: str | None = ...,
- config: t.Any | None = ...,
- **kwargs: t.Any,
- ) -> None:
- ...
- def __init__(
- self,
- default_value: t.Any = Undefined,
- allow_none: bool = False,
- read_only: bool | None = None,
- help: str | None = None,
- config: t.Any | None = None,
- **kwargs: t.Any,
- ) -> None:
- self.min = kwargs.pop("min", None)
- self.max = kwargs.pop("max", None)
- super().__init__(
- default_value=default_value,
- allow_none=allow_none,
- read_only=read_only,
- help=help,
- config=config,
- **kwargs,
- )
- def validate(self, obj: t.Any, value: t.Any) -> G:
- if not isinstance(value, int):
- self.error(obj, value)
- return t.cast(G, _validate_bounds(self, obj, value))
- def from_string(self, s: str) -> G:
- if self.allow_none and s == "None":
- return t.cast(G, None)
- return t.cast(G, int(s))
- def subclass_init(self, cls: type[t.Any]) -> None:
- pass # fully opt out of instance_init
- class CInt(Int[G, S]):
- """A casting version of the int trait."""
- if t.TYPE_CHECKING:
- @t.overload
- def __init__(
- self: CInt[int, t.Any],
- default_value: t.Any | Sentinel = ...,
- allow_none: Literal[False] = ...,
- read_only: bool | None = ...,
- help: str | None = ...,
- config: t.Any | None = ...,
- **kwargs: t.Any,
- ) -> None:
- ...
- @t.overload
- def __init__(
- self: CInt[int | None, t.Any],
- default_value: t.Any | Sentinel | None = ...,
- allow_none: Literal[True] = ...,
- read_only: bool | None = ...,
- help: str | None = ...,
- config: t.Any | None = ...,
- **kwargs: t.Any,
- ) -> None:
- ...
- def __init__(
- self: CInt[int | None, t.Any],
- default_value: t.Any | Sentinel | None = ...,
- allow_none: bool = ...,
- read_only: bool | None = ...,
- help: str | None = ...,
- config: t.Any | None = ...,
- **kwargs: t.Any,
- ) -> None:
- ...
- def validate(self, obj: t.Any, value: t.Any) -> G:
- try:
- value = int(value)
- except Exception:
- self.error(obj, value)
- return t.cast(G, _validate_bounds(self, obj, value))
- Long, CLong = Int, CInt
- Integer = Int
- class Float(TraitType[G, S]):
- """A float trait."""
- default_value = 0.0
- info_text = "a float"
- @t.overload
- def __init__(
- self: Float[float, int | float],
- default_value: float | Sentinel = ...,
- allow_none: Literal[False] = ...,
- read_only: bool | None = ...,
- help: str | None = ...,
- config: t.Any | None = ...,
- **kwargs: t.Any,
- ) -> None:
- ...
- @t.overload
- def __init__(
- self: Float[int | None, int | float | None],
- default_value: float | Sentinel | None = ...,
- allow_none: Literal[True] = ...,
- read_only: bool | None = ...,
- help: str | None = ...,
- config: t.Any | None = ...,
- **kwargs: t.Any,
- ) -> None:
- ...
- def __init__(
- self: Float[int | None, int | float | None],
- default_value: float | Sentinel | None = Undefined,
- allow_none: bool = False,
- read_only: bool | None = False,
- help: str | None = None,
- config: t.Any | None = None,
- **kwargs: t.Any,
- ) -> None:
- self.min = kwargs.pop("min", -float("inf"))
- self.max = kwargs.pop("max", float("inf"))
- super().__init__(
- default_value=default_value,
- allow_none=allow_none,
- read_only=read_only,
- help=help,
- config=config,
- **kwargs,
- )
- def validate(self, obj: t.Any, value: t.Any) -> G:
- if isinstance(value, int):
- value = float(value)
- if not isinstance(value, float):
- self.error(obj, value)
- return t.cast(G, _validate_bounds(self, obj, value))
- def from_string(self, s: str) -> G:
- if self.allow_none and s == "None":
- return t.cast(G, None)
- return t.cast(G, float(s))
- def subclass_init(self, cls: type[t.Any]) -> None:
- pass # fully opt out of instance_init
- class CFloat(Float[G, S]):
- """A casting version of the float trait."""
- if t.TYPE_CHECKING:
- @t.overload
- def __init__(
- self: CFloat[float, t.Any],
- default_value: t.Any = ...,
- allow_none: Literal[False] = ...,
- read_only: bool | None = ...,
- help: str | None = ...,
- config: t.Any | None = ...,
- **kwargs: t.Any,
- ) -> None:
- ...
- @t.overload
- def __init__(
- self: CFloat[float | None, t.Any],
- default_value: t.Any = ...,
- allow_none: Literal[True] = ...,
- read_only: bool | None = ...,
- help: str | None = ...,
- config: t.Any | None = ...,
- **kwargs: t.Any,
- ) -> None:
- ...
- def __init__(
- self: CFloat[float | None, t.Any],
- default_value: t.Any = ...,
- allow_none: bool = ...,
- read_only: bool | None = ...,
- help: str | None = ...,
- config: t.Any | None = ...,
- **kwargs: t.Any,
- ) -> None:
- ...
- def validate(self, obj: t.Any, value: t.Any) -> G:
- try:
- value = float(value)
- except Exception:
- self.error(obj, value)
- return t.cast(G, _validate_bounds(self, obj, value))
- class Complex(TraitType[complex, t.Union[complex, float, int]]):
- """A trait for complex numbers."""
- default_value = 0.0 + 0.0j
- info_text = "a complex number"
- def validate(self, obj: t.Any, value: t.Any) -> complex | None:
- if isinstance(value, complex):
- return value
- if isinstance(value, (float, int)):
- return complex(value)
- self.error(obj, value)
- def from_string(self, s: str) -> complex | None:
- if self.allow_none and s == "None":
- return None
- return complex(s)
- def subclass_init(self, cls: type[t.Any]) -> None:
- pass # fully opt out of instance_init
- class CComplex(Complex, TraitType[complex, t.Any]):
- """A casting version of the complex number trait."""
- def validate(self, obj: t.Any, value: t.Any) -> complex | None:
- try:
- return complex(value)
- except Exception:
- self.error(obj, value)
- # We should always be explicit about whether we're using bytes or unicode, both
- # for Python 3 conversion and for reliable unicode behaviour on Python 2. So
- # we don't have a Str type.
- class Bytes(TraitType[bytes, bytes]):
- """A trait for byte strings."""
- default_value = b""
- info_text = "a bytes object"
- def validate(self, obj: t.Any, value: t.Any) -> bytes | None:
- if isinstance(value, bytes):
- return value
- self.error(obj, value)
- def from_string(self, s: str) -> bytes | None:
- if self.allow_none and s == "None":
- return None
- if len(s) >= 3:
- # handle deprecated b"string"
- for quote in ('"', "'"):
- if s[:2] == f"b{quote}" and s[-1] == quote:
- old_s = s
- s = s[2:-1]
- warn(
- "Supporting extra quotes around Bytes is deprecated in traitlets 5.0. "
- f"Use {s!r} instead of {old_s!r}.",
- DeprecationWarning,
- stacklevel=2,
- )
- break
- return s.encode("utf8")
- def subclass_init(self, cls: type[t.Any]) -> None:
- pass # fully opt out of instance_init
- class CBytes(Bytes, TraitType[bytes, t.Any]):
- """A casting version of the byte string trait."""
- def validate(self, obj: t.Any, value: t.Any) -> bytes | None:
- try:
- return bytes(value)
- except Exception:
- self.error(obj, value)
- class Unicode(TraitType[G, S]):
- """A trait for unicode strings."""
- default_value = ""
- info_text = "a unicode string"
- if t.TYPE_CHECKING:
- @t.overload
- def __init__(
- self: Unicode[str, str | bytes],
- default_value: str | Sentinel = ...,
- allow_none: Literal[False] = ...,
- read_only: bool | None = ...,
- help: str | None = ...,
- config: t.Any = ...,
- **kwargs: t.Any,
- ) -> None:
- ...
- @t.overload
- def __init__(
- self: Unicode[str | None, str | bytes | None],
- default_value: str | Sentinel | None = ...,
- allow_none: Literal[True] = ...,
- read_only: bool | None = ...,
- help: str | None = ...,
- config: t.Any = ...,
- **kwargs: t.Any,
- ) -> None:
- ...
- def __init__(
- self: Unicode[str | None, str | bytes | None],
- default_value: str | Sentinel | None = ...,
- allow_none: bool = ...,
- read_only: bool | None = ...,
- help: str | None = ...,
- config: t.Any = ...,
- **kwargs: t.Any,
- ) -> None:
- ...
- def validate(self, obj: t.Any, value: t.Any) -> G:
- if isinstance(value, str):
- return t.cast(G, value)
- if isinstance(value, bytes):
- try:
- return t.cast(G, value.decode("ascii", "strict"))
- except UnicodeDecodeError as e:
- msg = "Could not decode {!r} for unicode trait '{}' of {} instance."
- raise TraitError(msg.format(value, self.name, class_of(obj))) from e
- self.error(obj, value)
- def from_string(self, s: str) -> G:
- if self.allow_none and s == "None":
- return t.cast(G, None)
- s = os.path.expanduser(s)
- if len(s) >= 2:
- # handle deprecated "1"
- for c in ('"', "'"):
- if s[0] == s[-1] == c:
- old_s = s
- s = s[1:-1]
- warn(
- "Supporting extra quotes around strings is deprecated in traitlets 5.0. "
- f"You can use {s!r} instead of {old_s!r} if you require traitlets >=5.",
- DeprecationWarning,
- stacklevel=2,
- )
- return t.cast(G, s)
- def subclass_init(self, cls: type[t.Any]) -> None:
- pass # fully opt out of instance_init
- class CUnicode(Unicode[G, S], TraitType[str, t.Any]):
- """A casting version of the unicode trait."""
- if t.TYPE_CHECKING:
- @t.overload
- def __init__(
- self: CUnicode[str, t.Any],
- default_value: str | Sentinel = ...,
- allow_none: Literal[False] = ...,
- read_only: bool | None = ...,
- help: str | None = ...,
- config: t.Any = ...,
- **kwargs: t.Any,
- ) -> None:
- ...
- @t.overload
- def __init__(
- self: CUnicode[str | None, t.Any],
- default_value: str | Sentinel | None = ...,
- allow_none: Literal[True] = ...,
- read_only: bool | None = ...,
- help: str | None = ...,
- config: t.Any = ...,
- **kwargs: t.Any,
- ) -> None:
- ...
- def __init__(
- self: CUnicode[str | None, t.Any],
- default_value: str | Sentinel | None = ...,
- allow_none: bool = ...,
- read_only: bool | None = ...,
- help: str | None = ...,
- config: t.Any = ...,
- **kwargs: t.Any,
- ) -> None:
- ...
- def validate(self, obj: t.Any, value: t.Any) -> G:
- try:
- return t.cast(G, str(value))
- except Exception:
- self.error(obj, value)
- class ObjectName(TraitType[str, str]):
- """A string holding a valid object name in this version of Python.
- This does not check that the name exists in any scope."""
- info_text = "a valid object identifier in Python"
- coerce_str = staticmethod(lambda _, s: s)
- def validate(self, obj: t.Any, value: t.Any) -> str:
- value = self.coerce_str(obj, value)
- if isinstance(value, str) and isidentifier(value):
- return value
- self.error(obj, value)
- def from_string(self, s: str) -> str | None:
- if self.allow_none and s == "None":
- return None
- return s
- class DottedObjectName(ObjectName):
- """A string holding a valid dotted object name in Python, such as A.b3._c"""
- def validate(self, obj: t.Any, value: t.Any) -> str:
- value = self.coerce_str(obj, value)
- if isinstance(value, str) and all(isidentifier(a) for a in value.split(".")):
- return value
- self.error(obj, value)
- class Bool(TraitType[G, S]):
- """A boolean (True, False) trait."""
- default_value = False
- info_text = "a boolean"
- if t.TYPE_CHECKING:
- @t.overload
- def __init__(
- self: Bool[bool, bool | int],
- default_value: bool | Sentinel = ...,
- allow_none: Literal[False] = ...,
- read_only: bool | None = ...,
- help: str | None = ...,
- config: t.Any = ...,
- **kwargs: t.Any,
- ) -> None:
- ...
- @t.overload
- def __init__(
- self: Bool[bool | None, bool | int | None],
- default_value: bool | Sentinel | None = ...,
- allow_none: Literal[True] = ...,
- read_only: bool | None = ...,
- help: str | None = ...,
- config: t.Any = ...,
- **kwargs: t.Any,
- ) -> None:
- ...
- def __init__(
- self: Bool[bool | None, bool | int | None],
- default_value: bool | Sentinel | None = ...,
- allow_none: bool = ...,
- read_only: bool | None = ...,
- help: str | None = ...,
- config: t.Any = ...,
- **kwargs: t.Any,
- ) -> None:
- ...
- def validate(self, obj: t.Any, value: t.Any) -> G:
- if isinstance(value, bool):
- return t.cast(G, value)
- elif isinstance(value, int):
- if value == 1:
- return t.cast(G, True)
- elif value == 0:
- return t.cast(G, False)
- self.error(obj, value)
- def from_string(self, s: str) -> G:
- if self.allow_none and s == "None":
- return t.cast(G, None)
- s = s.lower()
- if s in {"true", "1"}:
- return t.cast(G, True)
- elif s in {"false", "0"}:
- return t.cast(G, False)
- else:
- raise ValueError("%r is not 1, 0, true, or false")
- def subclass_init(self, cls: type[t.Any]) -> None:
- pass # fully opt out of instance_init
- def argcompleter(self, **kwargs: t.Any) -> list[str]:
- """Completion hints for argcomplete"""
- completions = ["true", "1", "false", "0"]
- if self.allow_none:
- completions.append("None")
- return completions
- class CBool(Bool[G, S]):
- """A casting version of the boolean trait."""
- if t.TYPE_CHECKING:
- @t.overload
- def __init__(
- self: CBool[bool, t.Any],
- default_value: bool | Sentinel = ...,
- allow_none: Literal[False] = ...,
- read_only: bool | None = ...,
- help: str | None = ...,
- config: t.Any = ...,
- **kwargs: t.Any,
- ) -> None:
- ...
- @t.overload
- def __init__(
- self: CBool[bool | None, t.Any],
- default_value: bool | Sentinel | None = ...,
- allow_none: Literal[True] = ...,
- read_only: bool | None = ...,
- help: str | None = ...,
- config: t.Any = ...,
- **kwargs: t.Any,
- ) -> None:
- ...
- def __init__(
- self: CBool[bool | None, t.Any],
- default_value: bool | Sentinel | None = ...,
- allow_none: bool = ...,
- read_only: bool | None = ...,
- help: str | None = ...,
- config: t.Any = ...,
- **kwargs: t.Any,
- ) -> None:
- ...
- def validate(self, obj: t.Any, value: t.Any) -> G:
- try:
- return t.cast(G, bool(value))
- except Exception:
- self.error(obj, value)
- class Enum(TraitType[G, G]):
- """An enum whose value must be in a given sequence."""
- if t.TYPE_CHECKING:
- @t.overload
- def __init__(
- self: Enum[G],
- values: t.Sequence[G],
- default_value: G | Sentinel = ...,
- allow_none: Literal[False] = ...,
- read_only: bool | None = ...,
- help: str | None = ...,
- config: t.Any = ...,
- **kwargs: t.Any,
- ) -> None:
- ...
- @t.overload
- def __init__(
- self: Enum[G | None],
- values: t.Sequence[G] | None,
- default_value: G | Sentinel | None = ...,
- allow_none: Literal[True] = ...,
- read_only: bool | None = ...,
- help: str | None = ...,
- config: t.Any = ...,
- **kwargs: t.Any,
- ) -> None:
- ...
- def __init__(
- self: Enum[G],
- values: t.Sequence[G] | None,
- default_value: G | Sentinel | None = Undefined,
- allow_none: bool = False,
- read_only: bool | None = None,
- help: str | None = None,
- config: t.Any = None,
- **kwargs: t.Any,
- ) -> None:
- self.values = values
- if allow_none is True and default_value is Undefined:
- default_value = None
- kwargs["allow_none"] = allow_none
- kwargs["read_only"] = read_only
- kwargs["help"] = help
- kwargs["config"] = config
- super().__init__(default_value, **kwargs)
- def validate(self, obj: t.Any, value: t.Any) -> G:
- if self.values and value in self.values:
- return t.cast(G, value)
- self.error(obj, value)
- def _choices_str(self, as_rst: bool = False) -> str:
- """Returns a description of the trait choices (not none)."""
- choices = self.values or []
- if as_rst:
- choice_str = "|".join("``%r``" % x for x in choices)
- else:
- choice_str = repr(list(choices))
- return choice_str
- def _info(self, as_rst: bool = False) -> str:
- """Returns a description of the trait."""
- none = " or %s" % ("`None`" if as_rst else "None") if self.allow_none else ""
- return f"any of {self._choices_str(as_rst)}{none}"
- def info(self) -> str:
- return self._info(as_rst=False)
- def info_rst(self) -> str:
- return self._info(as_rst=True)
- def from_string(self, s: str) -> G:
- try:
- return self.validate(None, s)
- except TraitError:
- return t.cast(G, _safe_literal_eval(s))
- def subclass_init(self, cls: type[t.Any]) -> None:
- pass # fully opt out of instance_init
- def argcompleter(self, **kwargs: t.Any) -> list[str]:
- """Completion hints for argcomplete"""
- return [str(v) for v in self.values or []]
- class CaselessStrEnum(Enum[G]):
- """An enum of strings where the case should be ignored."""
- def __init__(
- self: CaselessStrEnum[t.Any],
- values: t.Any,
- default_value: t.Any = Undefined,
- **kwargs: t.Any,
- ) -> None:
- super().__init__(values, default_value=default_value, **kwargs)
- def validate(self, obj: t.Any, value: t.Any) -> G:
- if not isinstance(value, str):
- self.error(obj, value)
- for v in self.values or []:
- assert isinstance(v, str)
- if v.lower() == value.lower():
- return t.cast(G, v)
- self.error(obj, value)
- def _info(self, as_rst: bool = False) -> str:
- """Returns a description of the trait."""
- none = " or %s" % ("`None`" if as_rst else "None") if self.allow_none else ""
- return f"any of {self._choices_str(as_rst)} (case-insensitive){none}"
- def info(self) -> str:
- return self._info(as_rst=False)
- def info_rst(self) -> str:
- return self._info(as_rst=True)
- class FuzzyEnum(Enum[G]):
- """An case-ignoring enum matching choices by unique prefixes/substrings."""
- case_sensitive = False
- #: If True, choices match anywhere in the string, otherwise match prefixes.
- substring_matching = False
- def __init__(
- self: FuzzyEnum[t.Any],
- values: t.Any,
- default_value: t.Any = Undefined,
- case_sensitive: bool = False,
- substring_matching: bool = False,
- **kwargs: t.Any,
- ) -> None:
- self.case_sensitive = case_sensitive
- self.substring_matching = substring_matching
- super().__init__(values, default_value=default_value, **kwargs)
- def validate(self, obj: t.Any, value: t.Any) -> G:
- if not isinstance(value, str):
- self.error(obj, value)
- conv_func = (lambda c: c) if self.case_sensitive else lambda c: c.lower()
- substring_matching = self.substring_matching
- match_func = (lambda v, c: v in c) if substring_matching else (lambda v, c: c.startswith(v))
- value = conv_func(value) # type:ignore[no-untyped-call]
- choices = self.values or []
- matches = [match_func(value, conv_func(c)) for c in choices] # type:ignore[no-untyped-call]
- if sum(matches) == 1:
- for v, m in zip(choices, matches):
- if m:
- return v
- self.error(obj, value)
- def _info(self, as_rst: bool = False) -> str:
- """Returns a description of the trait."""
- none = " or %s" % ("`None`" if as_rst else "None") if self.allow_none else ""
- case = "sensitive" if self.case_sensitive else "insensitive"
- substr = "substring" if self.substring_matching else "prefix"
- return f"any case-{case} {substr} of {self._choices_str(as_rst)}{none}"
- def info(self) -> str:
- return self._info(as_rst=False)
- def info_rst(self) -> str:
- return self._info(as_rst=True)
- class Container(Instance[T]):
- """An instance of a container (list, set, etc.)
- To be subclassed by overriding klass.
- """
- klass: type[T] | None = None
- _cast_types: t.Any = ()
- _valid_defaults = SequenceTypes
- _trait: t.Any = None
- _literal_from_string_pairs: t.Any = ("[]", "()")
- @t.overload
- def __init__(
- self: Container[T],
- *,
- allow_none: Literal[False],
- read_only: bool | None = ...,
- help: str | None = ...,
- config: t.Any | None = ...,
- **kwargs: t.Any,
- ) -> None:
- ...
- @t.overload
- def __init__(
- self: Container[T | None],
- *,
- allow_none: Literal[True],
- read_only: bool | None = ...,
- help: str | None = ...,
- config: t.Any | None = ...,
- **kwargs: t.Any,
- ) -> None:
- ...
- @t.overload
- def __init__(
- self: Container[T],
- *,
- trait: t.Any = ...,
- default_value: t.Any = ...,
- help: str = ...,
- read_only: bool = ...,
- config: t.Any = ...,
- **kwargs: t.Any,
- ) -> None:
- ...
- def __init__(
- self,
- trait: t.Any | None = None,
- default_value: t.Any = Undefined,
- help: str | None = None,
- read_only: bool | None = None,
- config: t.Any | None = None,
- **kwargs: t.Any,
- ) -> None:
- """Create a container trait type from a list, set, or tuple.
- The default value is created by doing ``List(default_value)``,
- which creates a copy of the ``default_value``.
- ``trait`` can be specified, which restricts the type of elements
- in the container to that TraitType.
- If only one arg is given and it is not a Trait, it is taken as
- ``default_value``:
- ``c = List([1, 2, 3])``
- Parameters
- ----------
- trait : TraitType [ optional ]
- the type for restricting the contents of the Container. If unspecified,
- types are not checked.
- default_value : SequenceType [ optional ]
- The default value for the Trait. Must be list/tuple/set, and
- will be cast to the container type.
- allow_none : bool [ default False ]
- Whether to allow the value to be None
- **kwargs : any
- further keys for extensions to the Trait (e.g. config)
- """
- # allow List([values]):
- if trait is not None and default_value is Undefined and not is_trait(trait):
- default_value = trait
- trait = None
- if default_value is None and not kwargs.get("allow_none", False):
- # improve backward-compatibility for possible subclasses
- # specifying default_value=None as default,
- # keeping 'unspecified' behavior (i.e. empty container)
- warn(
- f"Specifying {self.__class__.__name__}(default_value=None)"
- " for no default is deprecated in traitlets 5.0.5."
- " Use default_value=Undefined",
- DeprecationWarning,
- stacklevel=2,
- )
- default_value = Undefined
- if default_value is Undefined:
- args: t.Any = ()
- elif default_value is None:
- # default_value back on kwargs for super() to handle
- args = ()
- kwargs["default_value"] = None
- elif isinstance(default_value, self._valid_defaults):
- args = (default_value,)
- else:
- raise TypeError(f"default value of {self.__class__.__name__} was {default_value}")
- if is_trait(trait):
- if isinstance(trait, type):
- warn(
- "Traits should be given as instances, not types (for example, `Int()`, not `Int`)."
- " Passing types is deprecated in traitlets 4.1.",
- DeprecationWarning,
- stacklevel=3,
- )
- self._trait = trait() if isinstance(trait, type) else trait
- elif trait is not None:
- raise TypeError("`trait` must be a Trait or None, got %s" % repr_type(trait))
- super().__init__(
- klass=self.klass, args=args, help=help, read_only=read_only, config=config, **kwargs
- )
- def validate(self, obj: t.Any, value: t.Any) -> T | None:
- if isinstance(value, self._cast_types):
- assert self.klass is not None
- value = self.klass(value) # type:ignore[call-arg]
- value = super().validate(obj, value)
- if value is None:
- return value
- value = self.validate_elements(obj, value)
- return t.cast(T, value)
- def validate_elements(self, obj: t.Any, value: t.Any) -> T | None:
- validated = []
- if self._trait is None or isinstance(self._trait, Any):
- return t.cast(T, value)
- for v in value:
- try:
- v = self._trait._validate(obj, v)
- except TraitError as error:
- self.error(obj, v, error)
- else:
- validated.append(v)
- assert self.klass is not None
- return self.klass(validated) # type:ignore[call-arg]
- def class_init(self, cls: type[t.Any], name: str | None) -> None:
- if isinstance(self._trait, TraitType):
- self._trait.class_init(cls, None)
- super().class_init(cls, name)
- def subclass_init(self, cls: type[t.Any]) -> None:
- if isinstance(self._trait, TraitType):
- self._trait.subclass_init(cls)
- # explicitly not calling super().subclass_init(cls)
- # to opt out of instance_init
- def from_string(self, s: str) -> T | None:
- """Load value from a single string"""
- if not isinstance(s, str):
- raise TraitError(f"Expected string, got {s!r}")
- try:
- test = literal_eval(s)
- except Exception:
- test = None
- return self.validate(None, test)
- def from_string_list(self, s_list: list[str]) -> T | None:
- """Return the value from a list of config strings
- This is where we parse CLI configuration
- """
- assert self.klass is not None
- if len(s_list) == 1:
- # check for deprecated --Class.trait="['a', 'b', 'c']"
- r = s_list[0]
- if r == "None" and self.allow_none:
- return None
- if len(r) >= 2 and any(
- r.startswith(start) and r.endswith(end)
- for start, end in self._literal_from_string_pairs
- ):
- if self.this_class:
- clsname = self.this_class.__name__ + "."
- else:
- clsname = ""
- assert self.name is not None
- warn(
- "--{0}={1} for containers is deprecated in traitlets 5.0. "
- "You can pass `--{0} item` ... multiple times to add items to a list.".format(
- clsname + self.name, r
- ),
- DeprecationWarning,
- stacklevel=2,
- )
- return self.klass(literal_eval(r)) # type:ignore[call-arg]
- sig = inspect.signature(self.item_from_string)
- if "index" in sig.parameters:
- item_from_string = self.item_from_string
- else:
- # backward-compat: allow item_from_string to ignore index arg
- def item_from_string(s: str, index: int | None = None) -> T | str:
- return t.cast(T, self.item_from_string(s))
- return self.klass( # type:ignore[call-arg]
- [item_from_string(s, index=idx) for idx, s in enumerate(s_list)]
- )
- def item_from_string(self, s: str, index: int | None = None) -> T | str:
- """Cast a single item from a string
- Evaluated when parsing CLI configuration from a string
- """
- if self._trait:
- return t.cast(T, self._trait.from_string(s))
- else:
- return s
- class List(Container[t.List[T]]):
- """An instance of a Python list."""
- klass = list # type:ignore[assignment]
- _cast_types: t.Any = (tuple,)
- def __init__(
- self,
- trait: t.List[T] | t.Tuple[T] | t.Set[T] | Sentinel | TraitType[T, t.Any] | None = None,
- default_value: t.List[T] | t.Tuple[T] | t.Set[T] | Sentinel | None = Undefined,
- minlen: int = 0,
- maxlen: int = sys.maxsize,
- **kwargs: t.Any,
- ) -> None:
- """Create a List trait type from a list, set, or tuple.
- The default value is created by doing ``list(default_value)``,
- which creates a copy of the ``default_value``.
- ``trait`` can be specified, which restricts the type of elements
- in the container to that TraitType.
- If only one arg is given and it is not a Trait, it is taken as
- ``default_value``:
- ``c = List([1, 2, 3])``
- Parameters
- ----------
- trait : TraitType [ optional ]
- the type for restricting the contents of the Container.
- If unspecified, types are not checked.
- default_value : SequenceType [ optional ]
- The default value for the Trait. Must be list/tuple/set, and
- will be cast to the container type.
- minlen : Int [ default 0 ]
- The minimum length of the input list
- maxlen : Int [ default sys.maxsize ]
- The maximum length of the input list
- """
- self._maxlen = maxlen
- self._minlen = minlen
- super().__init__(trait=trait, default_value=default_value, **kwargs)
- def length_error(self, obj: t.Any, value: t.Any) -> None:
- e = (
- "The '%s' trait of %s instance must be of length %i <= L <= %i, but a value of %s was specified."
- % (self.name, class_of(obj), self._minlen, self._maxlen, value)
- )
- raise TraitError(e)
- def validate_elements(self, obj: t.Any, value: t.Any) -> t.Any:
- length = len(value)
- if length < self._minlen or length > self._maxlen:
- self.length_error(obj, value)
- return super().validate_elements(obj, value)
- def set(self, obj: t.Any, value: t.Any) -> None:
- if isinstance(value, str):
- return super().set(obj, [value]) # type:ignore[list-item]
- else:
- return super().set(obj, value)
- class Set(Container[t.Set[t.Any]]):
- """An instance of a Python set."""
- klass = set
- _cast_types = (tuple, list)
- _literal_from_string_pairs = ("[]", "()", "{}")
- # Redefine __init__ just to make the docstring more accurate.
- def __init__(
- self,
- trait: t.Any = None,
- default_value: t.Any = Undefined,
- minlen: int = 0,
- maxlen: int = sys.maxsize,
- **kwargs: t.Any,
- ) -> None:
- """Create a Set trait type from a list, set, or tuple.
- The default value is created by doing ``set(default_value)``,
- which creates a copy of the ``default_value``.
- ``trait`` can be specified, which restricts the type of elements
- in the container to that TraitType.
- If only one arg is given and it is not a Trait, it is taken as
- ``default_value``:
- ``c = Set({1, 2, 3})``
- Parameters
- ----------
- trait : TraitType [ optional ]
- the type for restricting the contents of the Container.
- If unspecified, types are not checked.
- default_value : SequenceType [ optional ]
- The default value for the Trait. Must be list/tuple/set, and
- will be cast to the container type.
- minlen : Int [ default 0 ]
- The minimum length of the input list
- maxlen : Int [ default sys.maxsize ]
- The maximum length of the input list
- """
- self._maxlen = maxlen
- self._minlen = minlen
- super().__init__(trait=trait, default_value=default_value, **kwargs)
- def length_error(self, obj: t.Any, value: t.Any) -> None:
- e = (
- "The '%s' trait of %s instance must be of length %i <= L <= %i, but a value of %s was specified."
- % (self.name, class_of(obj), self._minlen, self._maxlen, value)
- )
- raise TraitError(e)
- def validate_elements(self, obj: t.Any, value: t.Any) -> t.Any:
- length = len(value)
- if length < self._minlen or length > self._maxlen:
- self.length_error(obj, value)
- return super().validate_elements(obj, value)
- def set(self, obj: t.Any, value: t.Any) -> None:
- if isinstance(value, str):
- return super().set(obj, {value})
- else:
- return super().set(obj, value)
- def default_value_repr(self) -> str:
- # Ensure default value is sorted for a reproducible build
- list_repr = repr(sorted(self.make_dynamic_default() or []))
- if list_repr == "[]":
- return "set()"
- return "{" + list_repr[1:-1] + "}"
- class Tuple(Container[t.Tuple[t.Any, ...]]):
- """An instance of a Python tuple."""
- klass = tuple
- _cast_types = (list,)
- def __init__(self, *traits: t.Any, **kwargs: t.Any) -> None:
- """Create a tuple from a list, set, or tuple.
- Create a fixed-type tuple with Traits:
- ``t = Tuple(Int(), Str(), CStr())``
- would be length 3, with Int,Str,CStr for each element.
- If only one arg is given and it is not a Trait, it is taken as
- default_value:
- ``t = Tuple((1, 2, 3))``
- Otherwise, ``default_value`` *must* be specified by keyword.
- Parameters
- ----------
- *traits : TraitTypes [ optional ]
- the types for restricting the contents of the Tuple. If unspecified,
- types are not checked. If specified, then each positional argument
- corresponds to an element of the tuple. Tuples defined with traits
- are of fixed length.
- default_value : SequenceType [ optional ]
- The default value for the Tuple. Must be list/tuple/set, and
- will be cast to a tuple. If ``traits`` are specified,
- ``default_value`` must conform to the shape and type they specify.
- **kwargs
- Other kwargs passed to `Container`
- """
- default_value = kwargs.pop("default_value", Undefined)
- # allow Tuple((values,)):
- if len(traits) == 1 and default_value is Undefined and not is_trait(traits[0]):
- default_value = traits[0]
- traits = ()
- if default_value is None and not kwargs.get("allow_none", False):
- # improve backward-compatibility for possible subclasses
- # specifying default_value=None as default,
- # keeping 'unspecified' behavior (i.e. empty container)
- warn(
- f"Specifying {self.__class__.__name__}(default_value=None)"
- " for no default is deprecated in traitlets 5.0.5."
- " Use default_value=Undefined",
- DeprecationWarning,
- stacklevel=2,
- )
- default_value = Undefined
- if default_value is Undefined:
- args: t.Any = ()
- elif default_value is None:
- # default_value back on kwargs for super() to handle
- args = ()
- kwargs["default_value"] = None
- elif isinstance(default_value, self._valid_defaults):
- args = (default_value,)
- else:
- raise TypeError(f"default value of {self.__class__.__name__} was {default_value}")
- self._traits = []
- for trait in traits:
- if isinstance(trait, type):
- warn(
- "Traits should be given as instances, not types (for example, `Int()`, not `Int`)"
- " Passing types is deprecated in traitlets 4.1.",
- DeprecationWarning,
- stacklevel=2,
- )
- trait = trait()
- self._traits.append(trait)
- if self._traits and (default_value is None or default_value is Undefined):
- # don't allow default to be an empty container if length is specified
- args = None
- super(Container, self).__init__(klass=self.klass, args=args, **kwargs)
- def item_from_string(self, s: str, index: int) -> t.Any: # type:ignore[override]
- """Cast a single item from a string
- Evaluated when parsing CLI configuration from a string
- """
- if not self._traits or index >= len(self._traits):
- # return s instead of raising index error
- # length errors will be raised later on validation
- return s
- return self._traits[index].from_string(s)
- def validate_elements(self, obj: t.Any, value: t.Any) -> t.Any:
- if not self._traits:
- # nothing to validate
- return value
- if len(value) != len(self._traits):
- e = (
- "The '%s' trait of %s instance requires %i elements, but a value of %s was specified."
- % (self.name, class_of(obj), len(self._traits), repr_type(value))
- )
- raise TraitError(e)
- validated = []
- for trait, v in zip(self._traits, value):
- try:
- v = trait._validate(obj, v)
- except TraitError as error:
- self.error(obj, v, error)
- else:
- validated.append(v)
- return tuple(validated)
- def class_init(self, cls: type[t.Any], name: str | None) -> None:
- for trait in self._traits:
- if isinstance(trait, TraitType):
- trait.class_init(cls, None)
- super(Container, self).class_init(cls, name)
- def subclass_init(self, cls: type[t.Any]) -> None:
- for trait in self._traits:
- if isinstance(trait, TraitType):
- trait.subclass_init(cls)
- # explicitly not calling super().subclass_init(cls)
- # to opt out of instance_init
- class Dict(Instance["dict[K, V]"]):
- """An instance of a Python dict.
- One or more traits can be passed to the constructor
- to validate the keys and/or values of the dict.
- If you need more detailed validation,
- you may use a custom validator method.
- .. versionchanged:: 5.0
- Added key_trait for validating dict keys.
- .. versionchanged:: 5.0
- Deprecated ambiguous ``trait``, ``traits`` args in favor of ``value_trait``, ``per_key_traits``.
- """
- _value_trait = None
- _key_trait = None
- def __init__(
- self,
- value_trait: TraitType[t.Any, t.Any] | dict[K, V] | Sentinel | None = None,
- per_key_traits: t.Any = None,
- key_trait: TraitType[t.Any, t.Any] | None = None,
- default_value: dict[K, V] | Sentinel | None = Undefined,
- **kwargs: t.Any,
- ) -> None:
- """Create a dict trait type from a Python dict.
- The default value is created by doing ``dict(default_value)``,
- which creates a copy of the ``default_value``.
- Parameters
- ----------
- value_trait : TraitType [ optional ]
- The specified trait type to check and use to restrict the values of
- the dict. If unspecified, values are not checked.
- per_key_traits : Dictionary of {keys:trait types} [ optional, keyword-only ]
- A Python dictionary containing the types that are valid for
- restricting the values of the dict on a per-key basis.
- Each value in this dict should be a Trait for validating
- key_trait : TraitType [ optional, keyword-only ]
- The type for restricting the keys of the dict. If
- unspecified, the types of the keys are not checked.
- default_value : SequenceType [ optional, keyword-only ]
- The default value for the Dict. Must be dict, tuple, or None, and
- will be cast to a dict if not None. If any key or value traits are specified,
- the `default_value` must conform to the constraints.
- Examples
- --------
- a dict whose values must be text
- >>> d = Dict(Unicode())
- d2['n'] must be an integer
- d2['s'] must be text
- >>> d2 = Dict(per_key_traits={"n": Integer(), "s": Unicode()})
- d3's keys must be text
- d3's values must be integers
- >>> d3 = Dict(value_trait=Integer(), key_trait=Unicode())
- """
- # handle deprecated keywords
- trait = kwargs.pop("trait", None)
- if trait is not None:
- if value_trait is not None:
- raise TypeError(
- "Found a value for both `value_trait` and its deprecated alias `trait`."
- )
- value_trait = trait
- warn(
- "Keyword `trait` is deprecated in traitlets 5.0, use `value_trait` instead",
- DeprecationWarning,
- stacklevel=2,
- )
- traits = kwargs.pop("traits", None)
- if traits is not None:
- if per_key_traits is not None:
- raise TypeError(
- "Found a value for both `per_key_traits` and its deprecated alias `traits`."
- )
- per_key_traits = traits
- warn(
- "Keyword `traits` is deprecated in traitlets 5.0, use `per_key_traits` instead",
- DeprecationWarning,
- stacklevel=2,
- )
- # Handling positional arguments
- if default_value is Undefined and value_trait is not None:
- if not is_trait(value_trait):
- assert not isinstance(value_trait, TraitType)
- default_value = value_trait
- value_trait = None
- if key_trait is None and per_key_traits is not None:
- if is_trait(per_key_traits):
- key_trait = per_key_traits
- per_key_traits = None
- # Handling default value
- if default_value is Undefined:
- default_value = {}
- if default_value is None:
- args: t.Any = None
- elif isinstance(default_value, dict):
- args = (default_value,)
- elif isinstance(default_value, SequenceTypes):
- args = (default_value,)
- else:
- raise TypeError("default value of Dict was %s" % default_value)
- # Case where a type of TraitType is provided rather than an instance
- if is_trait(value_trait):
- if isinstance(value_trait, type):
- warn( # type:ignore[unreachable]
- "Traits should be given as instances, not types (for example, `Int()`, not `Int`)"
- " Passing types is deprecated in traitlets 4.1.",
- DeprecationWarning,
- stacklevel=2,
- )
- value_trait = value_trait()
- self._value_trait = value_trait
- elif value_trait is not None:
- raise TypeError(
- "`value_trait` must be a Trait or None, got %s" % repr_type(value_trait)
- )
- if is_trait(key_trait):
- if isinstance(key_trait, type):
- warn( # type:ignore[unreachable]
- "Traits should be given as instances, not types (for example, `Int()`, not `Int`)"
- " Passing types is deprecated in traitlets 4.1.",
- DeprecationWarning,
- stacklevel=2,
- )
- key_trait = key_trait()
- self._key_trait = key_trait
- elif key_trait is not None:
- raise TypeError("`key_trait` must be a Trait or None, got %s" % repr_type(key_trait))
- self._per_key_traits = per_key_traits
- super().__init__(klass=dict, args=args, **kwargs)
- def element_error(
- self, obj: t.Any, element: t.Any, validator: t.Any, side: str = "Values"
- ) -> None:
- e = (
- side
- + f" of the '{self.name}' trait of {class_of(obj)} instance must be {validator.info()}, but a value of {repr_type(element)} was specified."
- )
- raise TraitError(e)
- def validate(self, obj: t.Any, value: t.Any) -> dict[K, V] | None:
- value = super().validate(obj, value)
- if value is None:
- return value
- return self.validate_elements(obj, value)
- def validate_elements(self, obj: t.Any, value: dict[t.Any, t.Any]) -> dict[K, V] | None:
- per_key_override = self._per_key_traits or {}
- key_trait = self._key_trait
- value_trait = self._value_trait
- if not (key_trait or value_trait or per_key_override):
- return value
- validated = {}
- for key in value:
- v = value[key]
- if key_trait:
- try:
- key = key_trait._validate(obj, key)
- except TraitError:
- self.element_error(obj, key, key_trait, "Keys")
- active_value_trait = per_key_override.get(key, value_trait)
- if active_value_trait:
- try:
- v = active_value_trait._validate(obj, v)
- except TraitError:
- self.element_error(obj, v, active_value_trait, "Values")
- validated[key] = v
- return self.klass(validated) # type:ignore[misc,operator]
- def class_init(self, cls: type[t.Any], name: str | None) -> None:
- if isinstance(self._value_trait, TraitType):
- self._value_trait.class_init(cls, None)
- if isinstance(self._key_trait, TraitType):
- self._key_trait.class_init(cls, None)
- if self._per_key_traits is not None:
- for trait in self._per_key_traits.values():
- trait.class_init(cls, None)
- super().class_init(cls, name)
- def subclass_init(self, cls: type[t.Any]) -> None:
- if isinstance(self._value_trait, TraitType):
- self._value_trait.subclass_init(cls)
- if isinstance(self._key_trait, TraitType):
- self._key_trait.subclass_init(cls)
- if self._per_key_traits is not None:
- for trait in self._per_key_traits.values():
- trait.subclass_init(cls)
- # explicitly not calling super().subclass_init(cls)
- # to opt out of instance_init
- def from_string(self, s: str) -> dict[K, V] | None:
- """Load value from a single string"""
- if not isinstance(s, str):
- raise TypeError(f"from_string expects a string, got {s!r} of type {type(s)}")
- try:
- return t.cast("dict[K, V]", self.from_string_list([s]))
- except Exception:
- test = _safe_literal_eval(s)
- if isinstance(test, dict):
- return test
- raise
- def from_string_list(self, s_list: list[str]) -> t.Any:
- """Return a dict from a list of config strings.
- This is where we parse CLI configuration.
- Each item should have the form ``"key=value"``.
- item parsing is done in :meth:`.item_from_string`.
- """
- if len(s_list) == 1 and s_list[0] == "None" and self.allow_none:
- return None
- if len(s_list) == 1 and s_list[0].startswith("{") and s_list[0].endswith("}"):
- warn(
- f"--{self.name}={s_list[0]} for dict-traits is deprecated in traitlets 5.0. "
- f"You can pass --{self.name} <key=value> ... multiple times to add items to a dict.",
- DeprecationWarning,
- stacklevel=2,
- )
- return literal_eval(s_list[0])
- combined = {}
- for d in [self.item_from_string(s) for s in s_list]:
- combined.update(d)
- return combined
- def item_from_string(self, s: str) -> dict[K, V]:
- """Cast a single-key dict from a string.
- Evaluated when parsing CLI configuration from a string.
- Dicts expect strings of the form key=value.
- Returns a one-key dictionary,
- which will be merged in :meth:`.from_string_list`.
- """
- if "=" not in s:
- raise TraitError(
- f"'{self.__class__.__name__}' options must have the form 'key=value', got {s!r}"
- )
- key, value = s.split("=", 1)
- # cast key with key trait, if defined
- if self._key_trait:
- key = self._key_trait.from_string(key)
- # cast value with value trait, if defined (per-key or global)
- value_trait = (self._per_key_traits or {}).get(key, self._value_trait)
- if value_trait:
- value = value_trait.from_string(value)
- return t.cast("dict[K, V]", {key: value})
- class TCPAddress(TraitType[G, S]):
- """A trait for an (ip, port) tuple.
- This allows for both IPv4 IP addresses as well as hostnames.
- """
- default_value = ("127.0.0.1", 0)
- info_text = "an (ip, port) tuple"
- if t.TYPE_CHECKING:
- @t.overload
- def __init__(
- self: TCPAddress[tuple[str, int], tuple[str, int]],
- default_value: bool | Sentinel = ...,
- allow_none: Literal[False] = ...,
- read_only: bool | None = ...,
- help: str | None = ...,
- config: t.Any = ...,
- **kwargs: t.Any,
- ) -> None:
- ...
- @t.overload
- def __init__(
- self: TCPAddress[tuple[str, int] | None, tuple[str, int] | None],
- default_value: bool | None | Sentinel = ...,
- allow_none: Literal[True] = ...,
- read_only: bool | None = ...,
- help: str | None = ...,
- config: t.Any = ...,
- **kwargs: t.Any,
- ) -> None:
- ...
- def __init__(
- self: TCPAddress[tuple[str, int] | None, tuple[str, int] | None]
- | TCPAddress[tuple[str, int], tuple[str, int]],
- default_value: bool | None | Sentinel = Undefined,
- allow_none: Literal[True, False] = False,
- read_only: bool | None = None,
- help: str | None = None,
- config: t.Any = None,
- **kwargs: t.Any,
- ) -> None:
- ...
- def validate(self, obj: t.Any, value: t.Any) -> G:
- if isinstance(value, tuple):
- if len(value) == 2:
- if isinstance(value[0], str) and isinstance(value[1], int):
- port = value[1]
- if port >= 0 and port <= 65535:
- return t.cast(G, value)
- self.error(obj, value)
- def from_string(self, s: str) -> G:
- if self.allow_none and s == "None":
- return t.cast(G, None)
- if ":" not in s:
- raise ValueError("Require `ip:port`, got %r" % s)
- ip, port_str = s.split(":", 1)
- port = int(port_str)
- return t.cast(G, (ip, port))
- class CRegExp(TraitType["re.Pattern[t.Any]", t.Union["re.Pattern[t.Any]", str]]):
- """A casting compiled regular expression trait.
- Accepts both strings and compiled regular expressions. The resulting
- attribute will be a compiled regular expression."""
- info_text = "a regular expression"
- def validate(self, obj: t.Any, value: t.Any) -> re.Pattern[t.Any] | None:
- try:
- return re.compile(value)
- except Exception:
- self.error(obj, value)
- class UseEnum(TraitType[t.Any, t.Any]):
- """Use a Enum class as model for the data type description.
- Note that if no default-value is provided, the first enum-value is used
- as default-value.
- .. sourcecode:: python
- # -- SINCE: Python 3.4 (or install backport: pip install enum34)
- import enum
- from traitlets import HasTraits, UseEnum
- class Color(enum.Enum):
- red = 1 # -- IMPLICIT: default_value
- blue = 2
- green = 3
- class MyEntity(HasTraits):
- color = UseEnum(Color, default_value=Color.blue)
- entity = MyEntity(color=Color.red)
- entity.color = Color.green # USE: Enum-value (preferred)
- entity.color = "green" # USE: name (as string)
- entity.color = "Color.green" # USE: scoped-name (as string)
- entity.color = 3 # USE: number (as int)
- assert entity.color is Color.green
- """
- default_value: enum.Enum | None = None
- info_text = "Trait type adapter to a Enum class"
- def __init__(
- self, enum_class: type[t.Any], default_value: t.Any = None, **kwargs: t.Any
- ) -> None:
- assert issubclass(enum_class, enum.Enum), "REQUIRE: enum.Enum, but was: %r" % enum_class
- allow_none = kwargs.get("allow_none", False)
- if default_value is None and not allow_none:
- default_value = next(iter(enum_class.__members__.values()))
- super().__init__(default_value=default_value, **kwargs)
- self.enum_class = enum_class
- self.name_prefix = enum_class.__name__ + "."
- def select_by_number(self, value: int, default: t.Any = Undefined) -> t.Any:
- """Selects enum-value by using its number-constant."""
- assert isinstance(value, int)
- enum_members = self.enum_class.__members__
- for enum_item in enum_members.values():
- if enum_item.value == value:
- return enum_item
- # -- NOT FOUND:
- return default
- def select_by_name(self, value: str, default: t.Any = Undefined) -> t.Any:
- """Selects enum-value by using its name or scoped-name."""
- assert isinstance(value, str)
- if value.startswith(self.name_prefix):
- # -- SUPPORT SCOPED-NAMES, like: "Color.red" => "red"
- value = value.replace(self.name_prefix, "", 1)
- return self.enum_class.__members__.get(value, default)
- def validate(self, obj: t.Any, value: t.Any) -> t.Any:
- if isinstance(value, self.enum_class):
- return value
- elif isinstance(value, int):
- # -- CONVERT: number => enum_value (item)
- value2 = self.select_by_number(value)
- if value2 is not Undefined:
- return value2
- elif isinstance(value, str):
- # -- CONVERT: name or scoped_name (as string) => enum_value (item)
- value2 = self.select_by_name(value)
- if value2 is not Undefined:
- return value2
- elif value is None:
- if self.allow_none:
- return None
- else:
- return self.default_value
- self.error(obj, value)
- def _choices_str(self, as_rst: bool = False) -> str:
- """Returns a description of the trait choices (not none)."""
- choices = self.enum_class.__members__.keys()
- if as_rst:
- return "|".join("``%r``" % x for x in choices)
- else:
- return repr(list(choices)) # Listify because py3.4- prints odict-class
- def _info(self, as_rst: bool = False) -> str:
- """Returns a description of the trait."""
- none = " or %s" % ("`None`" if as_rst else "None") if self.allow_none else ""
- return f"any of {self._choices_str(as_rst)}{none}"
- def info(self) -> str:
- return self._info(as_rst=False)
- def info_rst(self) -> str:
- return self._info(as_rst=True)
- class Callable(TraitType[t.Callable[..., t.Any], t.Callable[..., t.Any]]):
- """A trait which is callable.
- Notes
- -----
- Classes are callable, as are instances
- with a __call__() method."""
- info_text = "a callable"
- def validate(self, obj: t.Any, value: t.Any) -> t.Any:
- if callable(value):
- return value
- else:
- self.error(obj, value)
|