33import re
44from dataclasses import dataclass , asdict , field as dataclass_field
55from ast import literal_eval
6+ from datetime import date , time , datetime
7+
8+ from .config import Config
69
710if TYPE_CHECKING :
811 from typing import Any , Callable , Generator , Self , Literal
912
13+ # =====================================
14+ # Basic classes: Value, Form, Flag, ...
15+ # =====================================
1016
11- #########################################
12- # Basic classes: Value, Form, Flag, ... #
13- #########################################
14-
15- type PlainValue = bool | int | float | str
17+ type PlainValue = bool | int | float | str | date | time | datetime
1618type Value = None | PlainValue | list [PlainValue ]
1719
1820
@@ -24,14 +26,9 @@ def as_plain(self) -> PlainValue | None:
2426 if self .v is None :
2527 return None
2628 if isinstance (self .v , list ):
27- if len (self .v ) == 0 :
28- return None
29- return self .v [0 ]
29+ return self .v [0 ] if len (self .v ) else None
3030 return self .v
3131
32- def as_str (self ) -> str | None :
33- return str (self .as_plain ())
34-
3532 def as_list (self ) -> list [PlainValue ]:
3633 if self .v is None :
3734 return []
@@ -40,13 +37,22 @@ def as_list(self) -> list[PlainValue]:
4037 else :
4138 return [self .v ]
4239
40+ def as_str (self ) -> str | None :
41+ v = self .as_plain ()
42+ return self ._strify (v ) if v is not None else None
43+
4344 def as_str_list (self ) -> list [str ]:
44- if self .v is None :
45- return []
46- elif isinstance (self .v , list ):
47- return [str (x ) for x in self .v ]
48- else :
49- return [str (self .v )]
45+ return [self ._strify (x ) for x in self .as_list ()]
46+
47+ @staticmethod
48+ def _strify (v : PlainValue ) -> str :
49+ if isinstance (v , datetime ):
50+ return v .strftime (Config .datetime_fmt )
51+ elif isinstance (v , date ):
52+ return v .strftime (Config .date_fmt )
53+ elif isinstance (v , time ):
54+ return v .strftime (Config .time_fmt )
55+ return str (v )
5056
5157
5258@dataclass (frozen = True )
@@ -77,9 +83,44 @@ class OperFlag(Flag):
7783 store : FlagStore = 'assign'
7884
7985
80- ############
81- # Registry #
82- ############
86+ # ========
87+ # Registry
88+ # ========
89+
90+
91+ def _bool_conv (v : str | None ) -> bool :
92+ v = v .lower ().strip () if v is not None else None
93+ if v in ('true' , 'yes' , '1' , 'on' , 'y' , '' ):
94+ return True
95+ if v in ('false' , 'no' , '0' , 'off' , 'n' , None ):
96+ return False
97+ # Same to :meth:`directives.flag`.
98+ raise ValueError (f'no argument is allowed; "{ v } " supplied' )
99+
100+
101+ def _str_conv (v : str ) -> str :
102+ try :
103+ vv = literal_eval (v )
104+ except (ValueError , SyntaxError ):
105+ return v
106+ return vv if isinstance (vv , str ) else v
107+
108+
109+ def _date_conv (v : str ) -> date :
110+ return datetime .strptime (v , Config .date_fmt ).date ()
111+
112+
113+ def _time_conv (v : str ) -> time :
114+ return datetime .strptime (v , Config .time_fmt ).time ()
115+
116+
117+ def _datetime_conv (v : str ) -> datetime :
118+ return datetime .strptime (v , Config .datetime_fmt )
119+
120+
121+ _required_flag = BoolFlag ('required' )
122+
123+ _sep_flag = OperFlag ('sep' , etype = str )
83124
84125
85126class Registry :
@@ -95,33 +136,19 @@ class Registry:
95136 'num' : float ,
96137 'str' : str ,
97138 'string' : str ,
139+ 'date' : date ,
140+ 'time' : time ,
141+ 'datetime' : datetime ,
98142 }
99143
100- """Internal type converters."""
101-
102- @staticmethod
103- def _bool_conv (v : str | None ) -> bool :
104- v = v .lower ().strip () if v is not None else None
105- if v in ('true' , 'yes' , '1' , 'on' , 'y' , '' ):
106- return True
107- if v in ('false' , 'no' , '0' , 'off' , 'n' , None ):
108- return False
109- # Same to :meth:`directives.flag`.
110- raise ValueError (f'no argument is allowed; "{ v } " supplied' )
111-
112- @staticmethod
113- def _str_conv (v : str ) -> str :
114- try :
115- vv = literal_eval (v )
116- except (ValueError , SyntaxError ):
117- return v
118- return vv if isinstance (vv , str ) else v
119-
120144 convs : dict [type , Callable [[str ], Any ]] = {
121145 bool : _bool_conv ,
122146 int : int ,
123147 float : float ,
124148 str : _str_conv ,
149+ date : _date_conv ,
150+ time : _time_conv ,
151+ datetime : _datetime_conv ,
125152 }
126153
127154 forms : dict [str , Form ] = {
@@ -132,9 +159,6 @@ def _str_conv(v: str) -> str:
132159
133160 """Builtin flags."""
134161
135- _required_flag = BoolFlag ('required' )
136- _sep_flag = OperFlag ('sep' , etype = str )
137-
138162 flags : dict [str , BoolFlag ] = {
139163 'required' : _required_flag ,
140164 'require' : _required_flag ,
@@ -147,9 +171,9 @@ def _str_conv(v: str) -> str:
147171 }
148172
149173
150- ##########################
151- # Data, Field and Schema #
152- ##########################
174+ # ======================
175+ # Data, Field and Schema
176+ # ======================
153177
154178
155179@dataclass
@@ -216,7 +240,7 @@ def parse(self, rawval: str | None) -> Value:
216240 if self .etype is bool :
217241 # Special case: A single bool field is valid even when
218242 # value is not supplied.
219- return Registry . _bool_conv (rawval )
243+ return _bool_conv (rawval )
220244 return None
221245
222246 # Strip whitespace. TODO: supported unchanged?
@@ -305,7 +329,7 @@ def _apply_modifier(self, mod: str):
305329
306330 self .field .etype = Registry .etypes [etype ]
307331 self .field .ctype = Registry .forms [form ].ctype
308- self .field .flags [Registry . _sep_flag .name ] = Registry .forms [form ].sep
332+ self .field .flags [_sep_flag .name ] = Registry .forms [form ].sep
309333 return
310334
311335 # Match: Type only (e.g., "int")
@@ -341,7 +365,7 @@ def _apply_modifier(self, mod: str):
341365 )
342366
343367 # Deal with builtin flags.
344- if flag == Registry . _sep_flag :
368+ if flag == _sep_flag :
345369 # ctype default to list if 'sep by' is used without a 'xxx of xxx'.
346370 if self .field .ctype is None :
347371 self .field .ctype = list
0 commit comments