55from __future__ import annotations
66
77from dataclasses import dataclass
8- from typing import Any
9-
10- import jsonschema
8+ from typing import Any , cast
119
1210from ._fragments import VALID_FRAGMENTS , Fragment
13- from ._humanize_validation_errors import errors_to_human_strings
1411from ._substitutions import Substituter
15- from ._validators import CustomValidator
1612from .exceptions import ConfigurationError
1713
1814
@@ -23,91 +19,72 @@ class Config:
2319 substitutions : list [Substituter ]
2420
2521
26- SCHEMA = {
27- "$schema" : CustomValidator .META_SCHEMA ["$id" ],
28- "type" : "object" ,
29- "properties" : {
30- "content-type" : {
31- "type" : "string" ,
32- "enum" : ["text/markdown" , "text/x-rst" ],
33- },
34- "fragments" : {
35- "type" : "array" ,
36- "minItems" : 1 ,
37- # Items are validated separately for better error messages.
38- "items" : {"type" : "object" },
39- },
40- "substitutions" : {
41- "type" : "array" ,
42- "items" : {
43- "type" : "object" ,
44- "properties" : {
45- "pattern" : {"type" : "string" , "regex" : True },
46- "replacement" : {"type" : "string" },
47- "ignore-case" : {"type" : "boolean" },
48- },
49- "required" : ["pattern" , "replacement" ],
50- "additionalProperties" : False ,
51- },
52- },
53- },
54- "required" : ["content-type" , "fragments" ],
55- "additionalProperties" : False ,
56- }
22+ _BASE = "tool.hatch.metadata.hooks.fancy-pypi-readme."
5723
5824
5925def load_and_validate_config (config : dict [str , Any ]) -> Config :
60- errs = sorted (
61- CustomValidator (SCHEMA ).iter_errors (config ),
62- key = jsonschema .exceptions .relevance ,
63- )
26+ errs = []
27+
28+ ct = config .get ("content-type" )
29+ if ct is None :
30+ errs .append (f"{ _BASE } content-type is missing." )
31+ elif ct not in ("text/markdown" , "text/x-rst" ):
32+ errs .append (
33+ f"{ _BASE } content-type: '{ ct } ' is not one of "
34+ "['text/markdown', 'text/x-rst']"
35+ )
36+
37+ try :
38+ fragments = _load_fragments (config .get ("fragments" ))
39+ except ConfigurationError as e :
40+ errs .extend (e .errors )
41+
42+ try :
43+ subs_cfg = config .get ("substitutions" , [])
44+ if not isinstance (subs_cfg , list ):
45+ raise ConfigurationError (
46+ [f"{ _BASE } substitutions must be an array." ]
47+ )
48+
49+ substitutions = [
50+ Substituter .from_config (sub_cfg ) for sub_cfg in subs_cfg
51+ ]
52+ except ConfigurationError as e :
53+ errs .extend (e .errors )
54+
6455 if errs :
65- raise ConfigurationError (errors_to_human_strings ( errs ) )
56+ raise ConfigurationError (errs )
6657
6758 return Config (
68- config ["content-type" ],
69- _load_fragments (config ["fragments" ]),
70- [
71- Substituter .from_config (sub_cfg )
72- for sub_cfg in config .get ("substitutions" , [])
73- ],
59+ content_type = cast (str , ct ),
60+ fragments = fragments ,
61+ substitutions = substitutions ,
7462 )
7563
7664
77- def _load_fragments (config : list [dict [str , str ]]) -> list [Fragment ]:
65+ def _load_fragments (config : list [dict [str , str ]] | None ) -> list [Fragment ]:
7866 """
7967 Load fragments from *config*.
80-
81- This is a bit more complicated because validating the fragments field using
82- `oneOf` leads to unhelpful error messages that are difficult to convert
83- into something humanly meaningful.
84-
85- So we detect first, validate using jsonschema and try to load them. They
86- still may fail loading if they refer to files and lack markers / the
87- pattern doesn't match.
8868 """
69+ if config is None :
70+ raise ConfigurationError ([f"{ _BASE } fragments is missing." ])
71+ if not config :
72+ raise ConfigurationError ([f"{ _BASE } fragments must not be empty." ])
73+
8974 frags = []
9075 errs = []
9176
92- for i , frag_cfg in enumerate ( config ) :
77+ for frag_cfg in config :
9378 for frag in VALID_FRAGMENTS :
9479 if frag .key not in frag_cfg :
9580 continue
9681
9782 try :
98- ves = sorted (
99- frag .validator .iter_errors (frag_cfg ),
100- key = jsonschema .exceptions .relevance ,
101- )
102- if ves :
103- raise ConfigurationError (
104- errors_to_human_strings (ves , ("fragments" , i ))
105- )
10683 frags .append (frag .from_config (frag_cfg ))
10784 except ConfigurationError as e :
10885 errs .extend (e .errors )
10986
110- # We have either detecte and added or detected and errored, but in
87+ # We have either detected and added or detected and errored, but in
11188 # any case we're done with this fragment.
11289 break
11390 else :
0 commit comments