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
66
7- from typing import Mapping , Union , Sequence , Optional
7+ from typing import Mapping , Union , Sequence , Optional , List
88
99from .versions import VersionRaw , Version , guess_version
1010from ._types import StrOrFile , IOBase
@@ -41,6 +41,8 @@ class Meta:
4141 A title for the pin.
4242 description:
4343 A detailed description of the pin contents.
44+ tags:
45+ Optional tags applied to the pin.
4446 created:
4547 Datetime the pin was created (TODO: document format).
4648 pin_hash:
@@ -63,6 +65,8 @@ class Meta:
6365
6466 """
6567
68+ _excluded : ClassVar ["set[str]" ] = {"name" , "version" , "local" }
69+
6670 title : Optional [str ]
6771 description : Optional [str ]
6872
@@ -82,10 +86,24 @@ class Meta:
8286 # pin_hash, created, etc.."
8387 version : VersionRaw
8488
89+ tags : Optional [List [str ]] = None
8590 name : Optional [str ] = None
8691 user : Mapping = field (default_factory = dict )
8792 local : Mapping = field (default_factory = dict )
8893
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+
89107 def to_dict (self ) -> Mapping :
90108 data = asdict (self )
91109
@@ -94,21 +112,37 @@ def to_dict(self) -> Mapping:
94112 def to_pin_dict (self ):
95113 d = self .to_dict ()
96114
97- del d ["name" ]
98- del d ["version" ]
99- del d ["local" ]
115+ for k in self ._excluded :
116+ del d [k ]
117+
118+ # TODO: once tag writing is implemented, delete this line
119+ del d ["tags" ]
100120
101121 return d
102122
103123 @classmethod
104124 def from_pin_dict (cls , data , pin_name , version , local = None ) -> "Meta" :
105-
106125 # TODO: re-arrange Meta argument positions to reflect what's been
107126 # learned about default arguments. e.g. title was not used at some
108127 # point in api_version 1
128+ all_field_names = {entry .name for entry in fields (Meta )}
129+
130+ keep_fields = all_field_names - cls ._excluded
131+
109132 extra = {"title" : None } if "title" not in data else {}
110133 local = {} if local is None else local
111- return cls (** data , ** extra , name = pin_name , version = version , local = local )
134+
135+ meta_data = {k : v for k , v in data .items () if k in keep_fields }
136+ unknown = {k : v for k , v in data .items () if k not in keep_fields }
137+
138+ return cls (
139+ ** meta_data ,
140+ ** extra ,
141+ name = pin_name ,
142+ version = version ,
143+ local = local ,
144+ unknown_fields = unknown ,
145+ )
112146
113147 def to_pin_yaml (self , f : Optional [IOBase ] = None ) -> "str | None" :
114148 data = self .to_pin_dict ()
0 commit comments