"""Matchers for the `text sequence type`_ (:py:class:`str`)... _text sequence type: https://docs.python.org/3/library/stdtypes.html#text-sequence-type-str"""importjsonimportreimporttypingastpfromcollections.abcimportMapping,Sequencefromurllib.parseimportparse_qs,urlparsefromjoythief.coreimportMatcher,MaybeMatcherfromjoythief.objectsimportInstanceOf
[docs]classJsonString(Matcher[str]):"""Matches any :py:class:`str` instance representing JSON. :param expected: What the result of parsing the JSON should be. If omitted, any valid JSON string is matched. """__ANYTHING=object()_expected:tp.Anydef__init__(self,expected:tp.Any=__ANYTHING):self._expected=InstanceOf(object)ifexpected==self.__ANYTHINGelseexpecteddef__eq__(self,other:tp.Any)->bool:ifnotisinstance(other,str):returnNotImplementedtry:returntp.cast(bool,json.loads(other)==self._expected)exceptjson.decoder.JSONDecodeError:returnFalsedef__repr__(self)->str:returnf"JsonString({self._expected!r})"
[docs]classStringMatching(Matcher[str]):"""Matches any :py:class:`str` instance matching a regular expression. :param pattern: Regex pattern to match, as a string or compiled pattern. :param flags: Any `flags`_ to compile a :py:class:`str` pattern with :raises ValueError: if flags are provided with a pre-compiled pattern. .. _flags: https://docs.python.org/3/library/re.html#flags """_pattern:re.Pattern[str]@tp.overloaddef__init__(self,pattern:re.Pattern[str]):...@tp.overloaddef__init__(self,pattern:str,*,flags:int=0):...def__init__(self,pattern:tp.Union[str,re.Pattern[str]],*,flags:int=0,):self._pattern=re.compile(pattern,flags=flags)def__eq__(self,other:tp.Any)->bool:ifnotisinstance(other,str):returnNotImplementedreturnself._pattern.match(other)isnotNonedef__repr__(self)->str:returnf"StringMatching({self._pattern!r})"
[docs]classUrlString(Matcher[str]):"""Matches any :py:class:`str` instance representing a URL. The string is parsed with :py:func:`~urllib.parse.urlparse` and compared attribute-by-attribute. Any attributes not provided are ignored. :param scheme: the scheme (e.g. ``"https"``) :param hostname: the hostname (e.g. ``"example.com"``) :param path: the path (e.g. ``"/some/path"``) :param query: the result of parsing the query string with :py:func:`~urllib.parse.parse_qs` :raises TypeError: if no arguments are provided. """_hostname:tp.Optional[MaybeMatcher[str]]_path:tp.Optional[MaybeMatcher[str]]_query:tp.Optional[Mapping[str,Sequence[str]]]_scheme:tp.Optional[MaybeMatcher[str]]def__init__(self,*,scheme:tp.Optional[MaybeMatcher[str]]=None,hostname:tp.Optional[MaybeMatcher[str]]=None,path:tp.Optional[MaybeMatcher[str]]=None,query:tp.Optional[Mapping[str,Sequence[str]]]=None,):self._hostname=hostnameself._path=pathself._query=queryself._scheme=schemeifall(getattr(self,attr)isNoneforattrin["_hostname","_path","_query","_scheme"]):raiseTypeError("A UrlString with no arguments matches any string")def__eq__(self,other:tp.Any)->bool:ifnotisinstance(other,str):returnNotImplementedparsed=urlparse(other)forattributein["hostname","path","scheme"]:if(expected:=getattr(self,f"_{attribute}"))isnotNoneandgetattr(parsed,attribute)!=expected:returnFalseif(expected:=self._query)isnotNoneandparse_qs(parsed.query,keep_blank_values=True)!=expected:returnFalsereturnTruedef__repr__(self)->str:parameters=[f"{name}={value!r}"fornamein["scheme","hostname","path","query"]if(value:=getattr(self,f"_{name}"))isnotNone]returnf"UrlString({', '.join(parameters)})"