Skip to content

Commit

Permalink
Have the names of matcher attributes match regexes.yaml
Browse files Browse the repository at this point in the history
It's kinda dumb that I didn't do that in the first place and I'm not
entirely sure why I missed it...

Anyway there is no reason to make `pattern` be the string pattern and
`regex` be the compiled pattern, that unnecessarily diverges from the
regexes.yaml naming for the corresponding attribute which is a shame,
and the compiled regex in python is... `re.Pattern`, so it makes more
sense on both axis to have `pattern: re.Pattern[str]` and `regex:
str`.

Also add a `regex_flag` attribute/property on the device matchers for
the string version of the flags.
  • Loading branch information
masklinn committed Jul 13, 2024
1 parent bb74478 commit 20d1cd8
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 45 deletions.
2 changes: 1 addition & 1 deletion src/ua_parser/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
...

Expand Down
46 changes: 25 additions & 21 deletions src/ua_parser/lazy.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class UserAgentMatcher(Matcher[UserAgent]):
"""

pattern: str = ""
regex: str = ""
family: str
major: Optional[str]
minor: Optional[str]
Expand All @@ -33,15 +33,15 @@ 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
self.patch = patch
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])
Expand All @@ -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 = [
Expand All @@ -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]):
Expand All @@ -81,7 +81,7 @@ class OSMatcher(Matcher[OS]):
"""

pattern: str = ""
regex: str = ""
family: str
major: str
minor: str
Expand All @@ -97,15 +97,15 @@ 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"
self.patch = patch or "$4"
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}")
Expand All @@ -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 = [
Expand All @@ -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]):
Expand All @@ -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
Expand All @@ -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}")
Expand All @@ -176,17 +176,21 @@ 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 = [
("family", self.family if self.family != "$1" else None),
("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})"
44 changes: 25 additions & 19 deletions src/ua_parser/matchers.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class UserAgentMatcher(Matcher[UserAgent]):
"""

regex: Pattern[str]
pattern: Pattern[str]
family: str
major: Optional[str]
minor: Optional[str]
Expand All @@ -29,15 +29,15 @@ 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
self.patch = patch
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])
Expand All @@ -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 = [
Expand All @@ -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]):
Expand All @@ -74,7 +74,7 @@ class OSMatcher(Matcher[OS]):
"""

regex: Pattern[str]
pattern: Pattern[str]
family: str
major: str
minor: str
Expand All @@ -90,15 +90,15 @@ 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"
self.patch = patch or "$4"
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}")
Expand All @@ -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 = [
Expand All @@ -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]):
Expand All @@ -134,7 +134,7 @@ class DeviceMatcher(Matcher[Device]):
"""

regex: Pattern[str]
pattern: Pattern[str]
family: str
brand: str
model: str
Expand All @@ -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}")
Expand All @@ -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 = [
Expand All @@ -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})"
8 changes: 4 additions & 4 deletions src/ua_parser/re2.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,15 @@ 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()

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()
Expand All @@ -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()
Expand Down

0 comments on commit 20d1cd8

Please sign in to comment.