diff --git a/src/ua_parser/__init__.py b/src/ua_parser/__init__.py index 54e49a0..a9a09b4 100644 --- a/src/ua_parser/__init__.py +++ b/src/ua_parser/__init__.py @@ -40,7 +40,7 @@ "parse_user_agent", ] -import contextlib +import importlib.util from typing import Callable, Optional from .basic import Resolver as BasicResolver @@ -59,7 +59,7 @@ from .loaders import load_builtins, load_lazy_builtins Re2Resolver: Optional[Callable[[Matchers], Resolver]] = None -with contextlib.suppress(ImportError): +if importlib.util.find_spec("re2"): from .re2 import Resolver as Re2Resolver diff --git a/src/ua_parser/core.py b/src/ua_parser/core.py index 5a1bea4..8ea880d 100644 --- a/src/ua_parser/core.py +++ b/src/ua_parser/core.py @@ -240,7 +240,7 @@ def __call__(self, ua: str) -> Optional[T]: @property @abc.abstractmethod - def pattern(self) -> str: + def regex(self) -> str: """Returns the matcher's pattern.""" ... diff --git a/src/ua_parser/lazy.py b/src/ua_parser/lazy.py index bf0ff7f..c5aa5e2 100644 --- a/src/ua_parser/lazy.py +++ b/src/ua_parser/lazy.py @@ -17,7 +17,7 @@ class UserAgentMatcher(Matcher[UserAgent]): """ - pattern: str = "" + regex: str = "" family: str major: Optional[str] minor: Optional[str] @@ -33,7 +33,7 @@ def __init__( patch: Optional[str] = None, patch_minor: Optional[str] = None, ) -> None: - self.pattern = regex + self.regex = regex self.family = family or "$1" self.major = major self.minor = minor @@ -41,7 +41,7 @@ def __init__( self.patch_minor = patch_minor def __call__(self, ua: str) -> Optional[UserAgent]: - if m := self.regex.search(ua): + if m := self.pattern.search(ua): return UserAgent( family=( self.family.replace("$1", m[1]) @@ -56,8 +56,8 @@ def __call__(self, ua: str) -> Optional[UserAgent]: return None @cached_property - def regex(self) -> Pattern[str]: - return re.compile(self.pattern) + def pattern(self) -> Pattern[str]: + return re.compile(self.regex) def __repr__(self) -> str: fields = [ @@ -69,7 +69,7 @@ def __repr__(self) -> str: ] args = "".join(f", {k}={v!r}" for k, v in fields if v is not None) - return f"UserAgentMatcher({self.pattern!r}{args})" + return f"UserAgentMatcher({self.regex!r}{args})" class OSMatcher(Matcher[OS]): @@ -81,7 +81,7 @@ class OSMatcher(Matcher[OS]): """ - pattern: str = "" + regex: str = "" family: str major: str minor: str @@ -97,7 +97,7 @@ def __init__( patch: Optional[str] = None, patch_minor: Optional[str] = None, ) -> None: - self.pattern = regex + self.regex = regex self.family = family or "$1" self.major = major or "$2" self.minor = minor or "$3" @@ -105,7 +105,7 @@ def __init__( self.patch_minor = patch_minor or "$5" def __call__(self, ua: str) -> Optional[OS]: - if m := self.regex.search(ua): + if m := self.pattern.search(ua): family = replacer(self.family, m) if family is None: raise ValueError(f"Unable to find OS family in {ua}") @@ -119,8 +119,8 @@ def __call__(self, ua: str) -> Optional[OS]: return None @cached_property - def regex(self) -> Pattern[str]: - return re.compile(self.pattern) + def pattern(self) -> Pattern[str]: + return re.compile(self.regex) def __repr__(self) -> str: fields = [ @@ -132,7 +132,7 @@ def __repr__(self) -> str: ] args = "".join(f", {k}={v!r}" for k, v in fields if v is not None) - return f"OSMatcher({self.pattern!r}{args})" + return f"OSMatcher({self.regex!r}{args})" class DeviceMatcher(Matcher[Device]): @@ -144,8 +144,8 @@ class DeviceMatcher(Matcher[Device]): """ - pattern: str = "" - flags: int = 0 + regex: str = "" + regex_flag: Optional[Literal["i"]] = None family: str brand: str model: str @@ -158,14 +158,14 @@ def __init__( brand: Optional[str] = None, model: Optional[str] = None, ) -> None: - self.pattern = regex - self.flags = re.IGNORECASE if regex_flag == "i" else 0 + self.regex = regex + self.regex_flag = regex_flag self.family = family or "$1" self.brand = brand or "" self.model = model or "$1" def __call__(self, ua: str) -> Optional[Device]: - if m := self.regex.search(ua): + if m := self.pattern.search(ua): family = replacer(self.family, m) if family is None: raise ValueError(f"Unable to find device family in {ua}") @@ -176,9 +176,13 @@ def __call__(self, ua: str) -> Optional[Device]: ) return None + @property + def flags(self) -> int: + return re.IGNORECASE if self.regex_flag == "i" else 0 + @cached_property - def regex(self) -> Pattern[str]: - return re.compile(self.pattern, flags=self.flags) + def pattern(self) -> Pattern[str]: + return re.compile(self.regex, flags=self.flags) def __repr__(self) -> str: fields = [ @@ -186,7 +190,7 @@ def __repr__(self) -> str: ("brand", self.brand or None), ("model", self.model if self.model != "$1" else None), ] - iflag = ', "i"' if self.flags & re.IGNORECASE else "" + iflag = ', "i"' if self.regex_flag else "" args = iflag + "".join(f", {k}={v!r}" for k, v in fields if v is not None) - return f"DeviceMatcher({self.pattern!r}{args})" + return f"DeviceMatcher({self.regex!r}{args})" diff --git a/src/ua_parser/matchers.py b/src/ua_parser/matchers.py index 03cd460..3956b3b 100644 --- a/src/ua_parser/matchers.py +++ b/src/ua_parser/matchers.py @@ -13,7 +13,7 @@ class UserAgentMatcher(Matcher[UserAgent]): """ - regex: Pattern[str] + pattern: Pattern[str] family: str major: Optional[str] minor: Optional[str] @@ -29,7 +29,7 @@ def __init__( patch: Optional[str] = None, patch_minor: Optional[str] = None, ) -> None: - self.regex = re.compile(regex) + self.pattern = re.compile(regex) self.family = family or "$1" self.major = major self.minor = minor @@ -37,7 +37,7 @@ def __init__( self.patch_minor = patch_minor def __call__(self, ua: str) -> Optional[UserAgent]: - if m := self.regex.search(ua): + if m := self.pattern.search(ua): return UserAgent( family=( self.family.replace("$1", m[1]) @@ -52,8 +52,8 @@ def __call__(self, ua: str) -> Optional[UserAgent]: return None @property - def pattern(self) -> str: - return self.regex.pattern + def regex(self) -> str: + return self.pattern.pattern def __repr__(self) -> str: fields = [ @@ -65,7 +65,7 @@ def __repr__(self) -> str: ] args = "".join(f", {k}={v!r}" for k, v in fields if v is not None) - return f"UserAgentMatcher({self.pattern!r}{args})" + return f"UserAgentMatcher({self.regex!r}{args})" class OSMatcher(Matcher[OS]): @@ -74,7 +74,7 @@ class OSMatcher(Matcher[OS]): """ - regex: Pattern[str] + pattern: Pattern[str] family: str major: str minor: str @@ -90,7 +90,7 @@ def __init__( patch: Optional[str] = None, patch_minor: Optional[str] = None, ) -> None: - self.regex = re.compile(regex) + self.pattern = re.compile(regex) self.family = family or "$1" self.major = major or "$2" self.minor = minor or "$3" @@ -98,7 +98,7 @@ def __init__( self.patch_minor = patch_minor or "$5" def __call__(self, ua: str) -> Optional[OS]: - if m := self.regex.search(ua): + if m := self.pattern.search(ua): family = replacer(self.family, m) if family is None: raise ValueError(f"Unable to find OS family in {ua}") @@ -112,8 +112,8 @@ def __call__(self, ua: str) -> Optional[OS]: return None @property - def pattern(self) -> str: - return self.regex.pattern + def regex(self) -> str: + return self.pattern.pattern def __repr__(self) -> str: fields = [ @@ -125,7 +125,7 @@ def __repr__(self) -> str: ] args = "".join(f", {k}={v!r}" for k, v in fields if v is not None) - return f"OSMatcher({self.pattern!r}{args})" + return f"OSMatcher({self.regex!r}{args})" class DeviceMatcher(Matcher[Device]): @@ -134,7 +134,7 @@ class DeviceMatcher(Matcher[Device]): """ - regex: Pattern[str] + pattern: Pattern[str] family: str brand: str model: str @@ -147,13 +147,15 @@ def __init__( brand: Optional[str] = None, model: Optional[str] = None, ) -> None: - self.regex = re.compile(regex, flags=re.IGNORECASE if regex_flag == "i" else 0) + self.pattern = re.compile( + regex, flags=re.IGNORECASE if regex_flag == "i" else 0 + ) self.family = family or "$1" self.brand = brand or "" self.model = model or "$1" def __call__(self, ua: str) -> Optional[Device]: - if m := self.regex.search(ua): + if m := self.pattern.search(ua): family = replacer(self.family, m) if family is None: raise ValueError(f"Unable to find device family in {ua}") @@ -165,12 +167,16 @@ def __call__(self, ua: str) -> Optional[Device]: return None @property - def pattern(self) -> str: - return self.regex.pattern + def regex(self) -> str: + return self.pattern.pattern + + @property + def regex_flag(self) -> str: + return "i" if self.flags & re.IGNORECASE else "" @property def flags(self) -> int: - return self.regex.flags + return self.pattern.flags def __repr__(self) -> str: fields = [ @@ -181,4 +187,4 @@ def __repr__(self) -> str: iflag = ', "i"' if self.flags & re.IGNORECASE else "" args = iflag + "".join(f", {k}={v!r}" for k, v in fields if v is not None) - return f"DeviceMatcher({self.pattern!r}{args})" + return f"DeviceMatcher({self.regex!r}{args})" diff --git a/src/ua_parser/re2.py b/src/ua_parser/re2.py index f3d95f7..83a4a14 100644 --- a/src/ua_parser/re2.py +++ b/src/ua_parser/re2.py @@ -38,7 +38,7 @@ def __init__( if self.user_agent_matchers: self.ua = re2.Filter() for u in self.user_agent_matchers: - self.ua.Add(u.pattern) + self.ua.Add(u.regex) self.ua.Compile() else: self.ua = DummyFilter() @@ -46,7 +46,7 @@ def __init__( if self.os_matchers: self.os = re2.Filter() for o in self.os_matchers: - self.os.Add(o.pattern) + self.os.Add(o.regex) self.os.Compile() else: self.os = DummyFilter() @@ -58,9 +58,9 @@ def __init__( # no pattern uses global flags, but since they're not # supported in JS that seems safe. if d.flags & re.IGNORECASE: - self.devices.Add("(?i)" + d.pattern) + self.devices.Add("(?i)" + d.regex) else: - self.devices.Add(d.pattern) + self.devices.Add(d.regex) self.devices.Compile() else: self.devices = DummyFilter() diff --git a/uap-core b/uap-core index 7b00298..d668d6c 160000 --- a/uap-core +++ b/uap-core @@ -1 +1 @@ -Subproject commit 7b002982f688dd11a23478cd1e101d2c72b2c0e7 +Subproject commit d668d6c6157db7737edfc0280adc6610c1b88029