11from  typing  import  ClassVar 
2- from  dataclasses  import  dataclass , asdict , field 
2+ from  dataclasses  import  dataclass , asdict , field ,  fields ,  InitVar 
33from  pathlib  import  Path 
44
55import  yaml 
@@ -65,6 +65,8 @@ class Meta:
6565
6666    """ 
6767
68+     _excluded : ClassVar ["set[str]" ] =  {"name" , "version" , "local" }
69+ 
6870    title : Optional [str ]
6971    description : Optional [str ]
7072
@@ -89,6 +91,19 @@ class Meta:
8991    user : Mapping  =  field (default_factory = dict )
9092    local : Mapping  =  field (default_factory = dict )
9193
94+     unknown_fields : InitVar ["dict | None" ] =  None 
95+ 
96+     def  __post_init__ (self , unknown_fields : "dict | None" ):
97+         unknown_fields  =  {} if  unknown_fields  is  None  else  unknown_fields 
98+ 
99+         self ._unknown_fields  =  unknown_fields 
100+ 
101+     def  __getattr__ (self , k ):
102+         try :
103+             return  self ._unknown_fields [k ]
104+         except  KeyError :
105+             raise  AttributeError (f"No metadata field not found: { k }  " )
106+ 
92107    def  to_dict (self ) ->  Mapping :
93108        data  =  asdict (self )
94109
@@ -97,24 +112,34 @@ def to_dict(self) -> Mapping:
97112    def  to_pin_dict (self ):
98113        d  =  self .to_dict ()
99114
100-         del  d ["name" ]
101-         del  d ["version" ]
102-         del  d ["local" ]
103- 
104-         # TODO: once tag writing is implemented, should keep tags field 
105-         del  d ["tags" ]
115+         for  k  in  self ._excluded :
116+             del  d [k ]
106117
107118        return  d 
108119
109120    @classmethod  
110121    def  from_pin_dict (cls , data , pin_name , version , local = None ) ->  "Meta" :
111- 
112122        # TODO: re-arrange Meta argument positions to reflect what's been 
113123        # learned about default arguments. e.g. title was not used at some 
114124        # point in api_version 1 
125+         all_field_names  =  {entry .name  for  entry  in  fields (Meta )}
126+ 
127+         keep_fields  =  all_field_names  -  cls ._excluded 
128+ 
115129        extra  =  {"title" : None } if  "title"  not  in   data  else  {}
116130        local  =  {} if  local  is  None  else  local 
117-         return  cls (** data , ** extra , name = pin_name , version = version , local = local )
131+ 
132+         meta_data  =  {k : v  for  k , v  in  data .items () if  k  in  keep_fields }
133+         unknown  =  {k : v  for  k , v  in  data .items () if  k  not  in   keep_fields }
134+ 
135+         return  cls (
136+             ** meta_data ,
137+             ** extra ,
138+             name = pin_name ,
139+             version = version ,
140+             local = local ,
141+             unknown_fields = unknown ,
142+         )
118143
119144    def  to_pin_yaml (self , f : Optional [IOBase ] =  None ) ->  "str | None" :
120145        data  =  self .to_pin_dict ()
0 commit comments