Skip to content

Commit

Permalink
feat: add type hints (#130)
Browse files Browse the repository at this point in the history
* refactor: replace deprecated function names

The camelCase functions have been marked as deprecated.
Replace with equivalent snake_case functions.

Move `set_name()` assignments to their parent `Word` when possible,
to address a type error since `set_results_name()` emits a `ParserElement`,
not a `Word`.

Signed-off-by: Mike Fiedler <[email protected]>

* feat: add type hints

- Add type hint to the one public interface for user agent parsing.
- Add testing step to CI
- Include type marker in package
- Set classifier metadata

Signed-off-by: Mike Fiedler <[email protected]>

---------

Signed-off-by: Mike Fiedler <[email protected]>
Co-authored-by: Dustin Ingram <[email protected]>
  • Loading branch information
miketheman and di authored Jul 5, 2023
1 parent 63ec25d commit 47ede61
Show file tree
Hide file tree
Showing 6 changed files with 35 additions and 30 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ jobs:
run: python -m pip install --upgrade pip setuptools wheel
- name: Install dependencies
run: python -m pip install -r requirements.txt
- name: Install lint dependencies
run: python -m pip install --upgrade mypy
- name: Lint
run: python -m mypy -p linehaul
- name: Install test dependencies
run: python -m pip install pytest pretend hypothesis pyaml
- name: Test
Expand Down
55 changes: 26 additions & 29 deletions linehaul/events/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
import cattr

from pyparsing import Literal as L, Word, Optional as OptionalItem
from pyparsing import printables as _printables, restOfLine
from pyparsing import printables as _printables, rest_of_line
from pyparsing import ParseException

from linehaul.ua import UserAgent, parser as user_agents
Expand All @@ -44,11 +44,11 @@ class UnparseableEvent(Exception):
pass


class NullValue:
class _NullValue:
pass


NullValue = NullValue()
NullValue = _NullValue()


printables = "".join(set(_printables + " " + "\t") - {"|", "@"})
Expand All @@ -58,29 +58,26 @@ class NullValue:
AT = L("@").suppress()

NULL = L("(null)")
NULL.setParseAction(lambda s, l, t: NullValue)
NULL.set_parse_action(lambda s, l, t: NullValue)

TIMESTAMP = Word(printables)
TIMESTAMP = TIMESTAMP.setResultsName("timestamp")
TIMESTAMP.setName("Timestamp")
TIMESTAMP = Word(printables).set_name("Timestamp")
TIMESTAMP = TIMESTAMP.set_results_name("timestamp")

COUNTRY_CODE = Word(printables)
COUNTRY_CODE = COUNTRY_CODE.setResultsName("country_code")
COUNTRY_CODE.setName("Country Code")
COUNTRY_CODE = Word(printables).set_name("Country Code")
COUNTRY_CODE = COUNTRY_CODE.set_results_name("country_code")

URL = Word(printables)
URL = URL.setResultsName("url")
URL.setName("URL")
URL = Word(printables).set_name("URL")
URL = URL.set_results_name("url")

REQUEST = TIMESTAMP + PIPE + OptionalItem(COUNTRY_CODE) + PIPE + URL

PROJECT_NAME = NULL | Word(printables)
PROJECT_NAME = PROJECT_NAME.setResultsName("project_name")
PROJECT_NAME.setName("Project Name")
PROJECT_NAME = PROJECT_NAME.set_results_name("project_name")
PROJECT_NAME.set_name("Project Name")

VERSION = NULL | Word(printables)
VERSION = VERSION.setResultsName("version")
VERSION.setName("Version")
VERSION = VERSION.set_results_name("version")
VERSION.set_name("Version")

PACKAGE_TYPE = NULL | (
L("sdist")
Expand All @@ -92,34 +89,34 @@ class NullValue:
| L("bdist_rpm")
| L("bdist_wininst")
)
PACKAGE_TYPE = PACKAGE_TYPE.setResultsName("package_type")
PACKAGE_TYPE.setName("Package Type")
PACKAGE_TYPE = PACKAGE_TYPE.set_results_name("package_type")
PACKAGE_TYPE.set_name("Package Type")

PROJECT = PROJECT_NAME + PIPE + VERSION + PIPE + PACKAGE_TYPE

TLS_PROTOCOL = NULL | Word(printables)
TLS_PROTOCOL = TLS_PROTOCOL.setResultsName("tls_protocol")
TLS_PROTOCOL.setName("TLS Protocol")
TLS_PROTOCOL = TLS_PROTOCOL.set_results_name("tls_protocol")
TLS_PROTOCOL.set_name("TLS Protocol")

TLS_CIPHER = NULL | Word(printables)
TLS_CIPHER = TLS_CIPHER.setResultsName("tls_cipher")
TLS_CIPHER.setName("TLS Cipher")
TLS_CIPHER = TLS_CIPHER.set_results_name("tls_cipher")
TLS_CIPHER.set_name("TLS Cipher")

TLS = TLS_PROTOCOL + PIPE + TLS_CIPHER

USER_AGENT = restOfLine
USER_AGENT = USER_AGENT.setResultsName("user_agent")
USER_AGENT.setName("UserAgent")
USER_AGENT = rest_of_line
USER_AGENT = USER_AGENT.set_results_name("user_agent")
USER_AGENT.set_name("UserAgent")

V1_HEADER = OptionalItem(L("1").suppress() + AT)

MESSAGE_v1 = V1_HEADER + REQUEST + PIPE + PROJECT + PIPE + USER_AGENT
MESSAGE_v1.leaveWhitespace()
MESSAGE_v1.leave_whitespace()

V2_HEADER = L("2").suppress() + AT

MESSAGE_v2 = V2_HEADER + REQUEST + PIPE + TLS + PIPE + PROJECT + PIPE + USER_AGENT
MESSAGE_v2.leaveWhitespace()
MESSAGE_v2.leave_whitespace()

V3_HEADER = L("download")
MESSAGE_v3 = (
Expand Down Expand Up @@ -204,7 +201,7 @@ def _value_or_none(value):

def parse(message):
try:
parsed = MESSAGE.parseString(message, parseAll=True)
parsed = MESSAGE.parse_string(message, parseAll=True)
except ParseException as exc:
raise UnparseableEvent("{!r} {}".format(message, exc)) from None

Expand Down
Empty file added linehaul/py.typed
Empty file.
2 changes: 1 addition & 1 deletion linehaul/ua/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,7 @@ def BrowserUserAgent():
)


def parse(user_agent):
def parse(user_agent: str) -> UserAgent | None:
try:
return cattr.structure(_parser(user_agent), UserAgent)
except UnableToParse:
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ classifiers = [
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3 :: Only",
"Typing :: Typed",
]

dependencies = [
Expand Down
3 changes: 3 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
[options]
packages = find:

[options.package_data]
* = py.typed

0 comments on commit 47ede61

Please sign in to comment.