"""Transformers for the API module: HttpMethod, Url, JsonPath."""from__future__importannotationsimportjsonfromtypingimportAnyVALID_METHODS=frozenset({"GET","POST","PUT","PATCH","DELETE","HEAD","OPTIONS"})
[docs]classHttpMethod:"""Validates and normalizes HTTP method strings."""def__init__(self,method:str)->None:"""Initialize and validate the HTTP method."""self.value=method.upper().strip()ifself.valuenotinVALID_METHODS:raiseValueError(f"Invalid HTTP method '{method}'. Valid methods: {sorted(VALID_METHODS)}")def__str__(self)->str:"""Return the method string."""returnself.valuedef__eq__(self,other:object)->bool:"""Compare with another HttpMethod or string."""ifisinstance(other,HttpMethod):returnself.value==other.valueifisinstance(other,str):returnself.value==other.upper()returnFalsedef__hash__(self)->int:"""Return hash of the method value."""returnhash(self.value)
[docs]classUrl:"""Represents a URL, resolving relative paths against a base URL. Args: url: The URL string (absolute or relative). base_url: Optional base URL for resolving relative URLs. """def__init__(self,url:str,base_url:str="")->None:"""Initialize and resolve the URL against the base URL."""self.raw=urlself.base_url=base_url.rstrip("/")self.value=self._resolve(url,base_url)@staticmethoddef_resolve(url:str,base_url:str)->str:"""Resolve *url* against *base_url* if *url* is relative."""ifurl.startswith(("http://","https://")):returnurlifnotbase_url:returnurlbase=base_url.rstrip("/")path=urlifurl.startswith("/")elsef"/{url}"returnf"{base}{path}"def__str__(self)->str:"""Return the resolved URL string."""returnself.valuedef__eq__(self,other:object)->bool:"""Compare with another Url or string."""ifisinstance(other,Url):returnself.value==other.valueifisinstance(other,str):returnself.value==otherreturnFalsedef__hash__(self)->int:"""Return hash of the URL value."""returnhash(self.value)
[docs]classJsonPath:"""Simple JSONPath evaluator supporting ``$.path.to.value`` syntax. Args: path: A JSONPath expression starting with ``$`` (e.g. ``"$.users[0].name"``). """def__init__(self,path:str)->None:"""Initialize and validate the JSONPath expression."""ifnotpath.startswith("$"):raiseValueError(f"JsonPath must start with '$', got: '{path}'")self.path=path
[docs]defevaluate(self,data:Any)->Any:"""Evaluate the path against *data* and return the matched value. Args: data: The JSON data to traverse (typically a dict or list). Returns: The value at the matched path. Raises: KeyError: If the path does not exist in *data*. """ifself.path=="$":returndata# Strip leading "$." or "$"expr=self.path[1:]ifexpr.startswith("."):expr=expr[1:]current:Any=data# Split on "." but handle array indexing [n]parts=self._tokenize(expr)forpartinparts:current=self._access(current,part)returncurrent
@staticmethoddef_tokenize(expr:str)->list[str]:"""Tokenize a path expression into keys and indices."""tokens:list[str]=[]current=""i=0whilei<len(expr):ch=expr[i]ifch==".":ifcurrent:tokens.append(current)current=""elifch=="[":ifcurrent:tokens.append(current)current=""j=expr.find("]",i)ifj==-1:raiseValueError(f"JsonPath has unclosed '[' in expression: '{expr}'")idx_str=expr[i+1:j]tokens.append(f"[{idx_str}]")i=jelse:current+=chi+=1ifcurrent:tokens.append(current)returntokens@staticmethoddef_access(current:Any,token:str)->Any:"""Access a single key or index from *current*."""iftoken.startswith("[")andtoken.endswith("]"):idx_str=token[1:-1]ifnotidx_str:raiseValueError(f"JsonPath has empty index '[]' in token: '{token}'")try:idx=int(idx_str)exceptValueErrorasexc:raiseValueError(f"JsonPath has non-numeric index '{idx_str}' in token: '{token}'")fromexcifnotisinstance(current,(list,tuple)):raiseKeyError(f"Cannot index non-list value with '{token}'.")ifidx<0oridx>=len(current):raiseKeyError(f"Index {idx} out of range for list of length {len(current)}.")returncurrent[idx]ifnotisinstance(current,dict):raiseKeyError(f"Cannot access key '{token}' on non-dict value.")iftokennotincurrent:raiseKeyError(f"Key '{token}' not found.")returncurrent[token]def__str__(self)->str:"""Return the path string."""returnself.path
[docs]defparse_json(text:str)->Any:"""Parse a JSON string, raising ValueError on invalid input. Args: text: A JSON string. Returns: The parsed JSON data. Raises: json.JSONDecodeError: If the text is not valid JSON. """returnjson.loads(text)