3939
4040logger = logging .getLogger (__name__ )
4141T = TypeVar ("T" )
42+ LabeledStrs = list [Union [str , dict [str , str ]]]
4243
4344
4445@overload
@@ -112,30 +113,35 @@ def __init__(self, vcard: vobject.base.Component,
112113 def __str__ (self ) -> str :
113114 return self .formatted_name
114115
115- def get_first (self , property : str , default : str = "" ) -> str :
116+ @overload
117+ def get_first (self , property : Literal ["n" ]) -> Optional [vobject .vcard .Name ]: ...
118+ @overload
119+ def get_first (self , property : Literal ["adr" ]) -> Optional [vobject .vcard .Address ]: ...
120+ @overload
121+ def get_first (self , property : str ) -> Optional [str ]: ...
122+ def get_first (self , property : str ) -> Union [None , str , vobject .vcard .Name ,
123+ vobject .vcard .Address ]:
116124 """Get a property from the underlying vCard.
117125
118126 This method should only be called for properties with cardinality \\ *1
119127 (zero or one). Otherwise only the first value will be returned. If the
120- property is not present a default will be returned .
128+ property is not present None will be retuned .
121129
122130 The type annotation for the return value is str but this is not
123131 enforced so it is up to the caller to make sure to only call this
124132 method for properties where the underlying vobject library returns a
125133 str.
126134
127135 :param property: the field value to get
128- :param default: the value to return if the vCard does not have this
129- property
130- :returns: the property value or the default
136+ :returns: the property value or None
131137 """
132138 try :
133139 return getattr (self .vcard , property ).value
134140 except AttributeError :
135- return default
141+ return None
136142
137- def _get_multi_property (self , name : str ) -> list :
138- """Get a vCard property that can exist more than once .
143+ def get_all (self , name : str ) -> list :
144+ """Get all values of the given vCard property .
139145
140146 It does not matter what the individual vcard properties store as their
141147 value. This function returns them untouched inside an aggregating
@@ -147,14 +153,12 @@ def _get_multi_property(self, name: str) -> list:
147153 :param name: the name of the property (should be UPPER case)
148154 :returns: the values from all occurrences of the named property
149155 """
150- values = []
151- for child in self .vcard .getChildren ():
152- if child .name == name :
153- ablabel = self ._get_ablabel (child )
154- if ablabel :
155- values .append ({ablabel : child .value })
156- else :
157- values .append (child .value )
156+ try :
157+ values = getattr (self .vcard , f"{ name .lower ()} _list" )
158+ except AttributeError :
159+ return []
160+ values = [{label : item .value } if (label := self ._get_ablabel (item ))
161+ else item .value for item in values ]
158162 return sorted (values , key = multi_property_key )
159163
160164 def _delete_vcard_object (self , name : str ) -> None :
@@ -259,7 +263,7 @@ def _get_types_for_vcard_object(self, object: vobject.base.ContentLine,
259263 return [default_type ]
260264
261265 @property
262- def version (self ) -> str :
266+ def version (self ) -> Optional [ str ] :
263267 return self .get_first ("version" )
264268
265269 @version .setter
@@ -274,7 +278,7 @@ def version(self, value: str) -> None:
274278 version .value = convert_to_vcard ("version" , value , ObjectType .str )
275279
276280 @property
277- def uid (self ) -> str :
281+ def uid (self ) -> Optional [ str ] :
278282 return self .get_first ("uid" )
279283
280284 @uid .setter
@@ -470,7 +474,7 @@ def _prepare_birthday_value(self, date: Date) -> tuple[Optional[str],
470474
471475 @property
472476 def kind (self ) -> str :
473- return self .get_first (self ._kind_attribute_name ().lower (), self ._default_kind )
477+ return self .get_first (self ._kind_attribute_name ().lower ()) or self ._default_kind
474478
475479 @kind .setter
476480 def kind (self , value : str ) -> None :
@@ -487,10 +491,15 @@ def _kind_attribute_name(self) -> str:
487491
488492 @property
489493 def formatted_name (self ) -> str :
490- return self .get_first ("fn" )
494+ fn = self .get_first ("fn" )
495+ if fn :
496+ return fn
497+ self .formatted_name = ""
498+ return self .get_first ("fn" ) or ""
491499
492500 @formatted_name .setter
493501 def formatted_name (self , value : str ) -> None :
502+ # TODO cardinality 1*
494503 """Set the FN field to the new value.
495504
496505 All previously existing FN fields are deleted. Version 4 of the specs
@@ -522,10 +531,9 @@ def _get_names_part(self, part: str) -> list[str]:
522531 the_list = getattr (self .vcard .n .value , part )
523532 except AttributeError :
524533 return []
525- else :
526- # check if list only contains empty strings
527- if not '' .join (the_list ):
528- return []
534+ # check if list only contains empty strings
535+ if not '' .join (the_list ):
536+ return []
529537 return the_list if isinstance (the_list , list ) else [the_list ]
530538
531539 def _get_name_prefixes (self ) -> list [str ]:
@@ -573,12 +581,16 @@ def get_last_name_first_name(self) -> str:
573581 return self .formatted_name
574582
575583 @property
576- def first_name (self ) -> str :
577- return list_to_string (self ._get_first_names (), " " )
584+ def first_name (self ) -> Optional [str ]:
585+ if parts := self ._get_first_names ():
586+ return list_to_string (parts , " " )
587+ return None
578588
579589 @property
580- def last_name (self ) -> str :
581- return list_to_string (self ._get_last_names (), " " )
590+ def last_name (self ) -> Optional [str ]:
591+ if parts := self ._get_last_names ():
592+ return list_to_string (parts , " " )
593+ return None
582594
583595 def _add_name (self , prefix : StrList , first_name : StrList ,
584596 additional_name : StrList , last_name : StrList ,
@@ -605,7 +617,7 @@ def organisations(self) -> list[Union[list[str], dict[str, list[str]]]]:
605617 """
606618 :returns: list of organisations, sorted alphabetically
607619 """
608- return self ._get_multi_property ( "ORG " )
620+ return self .get_all ( "org " )
609621
610622 def _add_organisation (self , organisation : StrList , label : Optional [str ] = None ) -> None :
611623 """Add one ORG entry to the underlying vcard
@@ -628,48 +640,47 @@ def _add_organisation(self, organisation: StrList, label: Optional[str] = None)
628640 showas_obj .value = "COMPANY"
629641
630642 @property
631- def titles (self ) -> list [ Union [ str , dict [ str , str ]]] :
632- return self ._get_multi_property ( "TITLE " )
643+ def titles (self ) -> LabeledStrs :
644+ return self .get_all ( "title " )
633645
634646 def _add_title (self , title : str , label : Optional [str ] = None ) -> None :
635647 self ._add_labelled_property ("title" , title , label , True )
636648
637649 @property
638- def roles (self ) -> list [ Union [ str , dict [ str , str ]]] :
639- return self ._get_multi_property ( "ROLE " )
650+ def roles (self ) -> LabeledStrs :
651+ return self .get_all ( "role " )
640652
641653 def _add_role (self , role : str , label : Optional [str ] = None ) -> None :
642654 self ._add_labelled_property ("role" , role , label , True )
643655
644656 @property
645- def nicknames (self ) -> list [ Union [ str , dict [ str , str ]]] :
646- return self ._get_multi_property ( "NICKNAME " )
657+ def nicknames (self ) -> LabeledStrs :
658+ return self .get_all ( "nickname " )
647659
648660 def _add_nickname (self , nickname : str , label : Optional [str ] = None ) -> None :
649661 self ._add_labelled_property ("nickname" , nickname , label , True )
650662
651663 @property
652- def notes (self ) -> list [ Union [ str , dict [ str , str ]]] :
653- return self ._get_multi_property ( "NOTE " )
664+ def notes (self ) -> LabeledStrs :
665+ return self .get_all ( "note " )
654666
655667 def _add_note (self , note : str , label : Optional [str ] = None ) -> None :
656668 self ._add_labelled_property ("note" , note , label , True )
657669
658670 @property
659- def webpages (self ) -> list [ Union [ str , dict [ str , str ]]] :
660- return self ._get_multi_property ( "URL " )
671+ def webpages (self ) -> LabeledStrs :
672+ return self .get_all ( "url " )
661673
662674 def _add_webpage (self , webpage : str , label : Optional [str ] = None ) -> None :
663675 self ._add_labelled_property ("url" , webpage , label , True )
664676
665677 @property
666678 def categories (self ) -> Union [list [str ], list [list [str ]]]:
667- category_list = []
668- for child in self .vcard .getChildren ():
669- if child .name == "CATEGORIES" :
670- value = child .value
671- category_list .append (
672- value if isinstance (value , list ) else [value ])
679+ category_list = self .get_all ("categories" )
680+ if not category_list :
681+ return category_list
682+ category_list = [value if isinstance (value , list ) else [value ] for
683+ value in category_list ]
673684 if len (category_list ) == 1 :
674685 return category_list [0 ]
675686 return sorted (category_list )
@@ -754,13 +765,16 @@ def emails(self) -> dict[str, list[str]]:
754765 :returns: dict of type and email address list
755766 """
756767 email_dict : dict [str , list [str ]] = {}
757- for child in self .vcard .getChildren ():
758- if child .name == "EMAIL" :
759- type = list_to_string (
760- self ._get_types_for_vcard_object (child , "internet" ), ", " )
761- if type not in email_dict :
762- email_dict [type ] = []
763- email_dict [type ].append (child .value )
768+ try :
769+ emails = self .vcard .email_list
770+ except AttributeError :
771+ return {}
772+ for child in emails :
773+ type = list_to_string (
774+ self ._get_types_for_vcard_object (child , "internet" ), ", " )
775+ if type not in email_dict :
776+ email_dict [type ] = []
777+ email_dict [type ].append (child .value )
764778 # sort email address lists
765779 for email_list in email_dict .values ():
766780 email_list .sort ()
@@ -805,19 +819,22 @@ def post_addresses(self) -> dict[str, list[PostAddress]]:
805819 :returns: dict of type and post address list
806820 """
807821 post_adr_dict : dict [str , list [PostAddress ]] = {}
808- for child in self .vcard .getChildren ():
809- if child .name == "ADR" :
810- type = list_to_string (self ._get_types_for_vcard_object (
811- child , "home" ), ", " )
812- if type not in post_adr_dict :
813- post_adr_dict [type ] = []
814- post_adr_dict [type ].append ({"box" : child .value .box ,
815- "extended" : child .value .extended ,
816- "street" : child .value .street ,
817- "code" : child .value .code ,
818- "city" : child .value .city ,
819- "region" : child .value .region ,
820- "country" : child .value .country })
822+ try :
823+ addresses = self .vcard .adr_list
824+ except AttributeError :
825+ return {}
826+ for child in addresses :
827+ type = list_to_string (self ._get_types_for_vcard_object (
828+ child , "home" ), ", " )
829+ if type not in post_adr_dict :
830+ post_adr_dict [type ] = []
831+ post_adr_dict [type ].append ({"box" : child .value .box ,
832+ "extended" : child .value .extended ,
833+ "street" : child .value .street ,
834+ "code" : child .value .code ,
835+ "city" : child .value .city ,
836+ "region" : child .value .region ,
837+ "country" : child .value .country })
821838 # sort post address lists
822839 for post_adr_list in post_adr_dict .values ():
823840 post_adr_list .sort (key = lambda x : (
@@ -930,9 +947,9 @@ def __init__(self, vcard: vobject.base.Component,
930947 # getters and setters
931948 #####################
932949
933- def _get_private_objects (self ) -> dict [str , list [ Union [ str , dict [ str , str ]]] ]:
950+ def _get_private_objects (self ) -> dict [str , LabeledStrs ]:
934951 supported = [x .lower () for x in self .supported_private_objects ]
935- private_objects : dict [str , list [ Union [ str , dict [ str , str ]]] ] = {}
952+ private_objects : dict [str , LabeledStrs ] = {}
936953 for child in self .vcard .getChildren ():
937954 lower = child .name .lower ()
938955 if lower .startswith ("x-" ) and lower [2 :] in supported :
@@ -1303,9 +1320,11 @@ def to_yaml(self) -> str:
13031320 "Note" : self .notes ,
13041321 "Webpage" : self .webpages ,
13051322 "Anniversary" :
1306- helpers .yaml_anniversary (self .anniversary , self .version ),
1323+ helpers .yaml_anniversary (self .anniversary , self .version or
1324+ self ._default_version ),
13071325 "Birthday" :
1308- helpers .yaml_anniversary (self .birthday , self .version ),
1326+ helpers .yaml_anniversary (self .birthday , self .version or
1327+ self ._default_version ),
13091328 "Address" : helpers .yaml_addresses (
13101329 self .post_addresses , ["Box" , "Extended" , "Street" , "Code" ,
13111330 "City" , "Region" , "Country" ], defaults = ["home" ])
0 commit comments