Coverage for archdocs/features/app_servers/parser.py: 100%

37 statements  

« prev     ^ index     » next       coverage.py v7.15.3, created at 2026-08-03 00:00 +0000

1import dataclasses 

2import re as py_re 

3import types 

4import typing 

5 

6from archdocs import prefilter, settings 

7from archdocs.features.app_servers.const import ApplicationServerEnum, ApplicationServerFeatures 

8 

9 

10@typing.final 

11@dataclasses.dataclass(kw_only=True, slots=True, frozen=True) 

12class _ServerSignature: 

13 server_name: str 

14 prefilter_literals: tuple[str, ...] 

15 compiled_pattern: py_re.Pattern[str] 

16 

17 

18# Servers not found by the module name alone: each has its own entry point, and werkzeug 

19# is also reachable through a literal wider than its package name. 

20_PATTERN_OF_SERVER: typing.Final = types.MappingProxyType( 

21 { 

22 ApplicationServerEnum.tornado_server: r"\btornado\.(?:httpserver|web|ioloop)\b", 

23 ApplicationServerEnum.gevent_server: r"\bgevent\.pywsgi\b", 

24 ApplicationServerEnum.eventlet_server: r"\beventlet\.wsgi\b", 

25 ApplicationServerEnum.werkzeug_server: r"\bwerkzeug\.serving\b|\brun_simple\s*\(", 

26 ApplicationServerEnum.wsgiref_server: r"\bwsgiref\.simple_server\b", 

27 }, 

28) 

29_LITERALS_OF_SERVER: typing.Final = types.MappingProxyType( 

30 {ApplicationServerEnum.werkzeug_server: ("werkzeug", "run_simple")}, 

31) 

32_SERVER_SIGNATURES: typing.Final = tuple( 

33 _ServerSignature( 

34 server_name=one_server.value, 

35 prefilter_literals=_LITERALS_OF_SERVER.get(one_server, (one_server.value,)), 

36 compiled_pattern=py_re.compile( 

37 _PATTERN_OF_SERVER.get( 

38 one_server, 

39 rf"\b(?:from|import)\s+{one_server.value}\b|\b{one_server.value}\s+(?:--\w|[\w.]+:\w+)", 

40 ), 

41 flags=settings.TYPICAL_RE_FLAGS, 

42 ), 

43 ) 

44 for one_server in ApplicationServerEnum 

45) 

46_WORKER_CLASS_LITERALS: typing.Final = ("worker_class", "--worker-class") 

47_WORKER_CLASS_PATTERN: typing.Final = py_re.compile( 

48 r"(?:worker_class\s*=|--worker-class)[\s=\"']*(?P<worker_class>[\w.]+)", 

49 flags=settings.TYPICAL_RE_FLAGS, 

50) 

51_WORKERS_COUNT_PATTERN: typing.Final = py_re.compile( 

52 r"(?:workers\s*=|--workers|\s-w)[\s=\"']*(?P<found_number>\d{1,3})\b", 

53 flags=settings.TYPICAL_RE_FLAGS, 

54) 

55_LISTEN_PORT_PATTERN: typing.Final = py_re.compile( 

56 r"(?:(?:port\s*=|--port)[\s=\"']*|(?:bind|--bind)[\s=\"']*[\w.\[\]]*:)(?P<found_number>\d{2,5})\b", 

57 flags=settings.TYPICAL_RE_FLAGS, 

58) 

59_TLS_PATTERN: typing.Final = py_re.compile( 

60 r"\bssl_cert\w*\b|\bssl_key\w*\b|\bcertfile\b|\bkeyfile\b|\bssl_context\b|--ssl-\w+", 

61 flags=settings.TYPICAL_RE_FLAGS, 

62) 

63_HTTP2_PATTERN: typing.Final = py_re.compile( 

64 r"\bhttp2\b|\bhttp\s*=\s*[\"']?2\b|--http[\s=]+2\b", 

65 flags=settings.TYPICAL_RE_FLAGS, 

66) 

67 

68 

69def _read_first_number(number_pattern: py_re.Pattern[str], raw_source: str, /) -> int: 

70 number_match: typing.Final = number_pattern.search(raw_source) 

71 if number_match is None: 

72 return 0 

73 return int(number_match.group("found_number")) 

74 

75 

76def _find_servers_behind_worker_class(raw_source: str, lowered_source: str, /) -> set[str]: 

77 if not prefilter.contains_any_literal(lowered_source, _WORKER_CLASS_LITERALS): 

78 return set() 

79 worker_class_match: typing.Final = _WORKER_CLASS_PATTERN.search(raw_source) 

80 if worker_class_match is None: 

81 return set() 

82 worker_class_name: typing.Final = worker_class_match.group("worker_class").lower() 

83 return {ApplicationServerEnum.gunicorn_server.value} | { 

84 one_server.value for one_server in ApplicationServerEnum if one_server.value in worker_class_name 

85 } 

86 

87 

88def find_application_server_features(raw_source: str) -> ApplicationServerFeatures: 

89 lowered_source: typing.Final = raw_source.lower() 

90 servers_found: typing.Final = { 

91 one_signature.server_name 

92 for one_signature in _SERVER_SIGNATURES 

93 if prefilter.contains_any_literal(lowered_source, one_signature.prefilter_literals) 

94 and one_signature.compiled_pattern.search(raw_source) 

95 } | _find_servers_behind_worker_class(raw_source, lowered_source) 

96 if not servers_found: 

97 return ApplicationServerFeatures() 

98 return ApplicationServerFeatures( 

99 servers_used=frozenset(servers_found), 

100 workers_count=_read_first_number(_WORKERS_COUNT_PATTERN, raw_source), 

101 listen_port=_read_first_number(_LISTEN_PORT_PATTERN, raw_source), 

102 tls_enabled=bool(_TLS_PATTERN.search(raw_source)), 

103 http2_enabled=bool(_HTTP2_PATTERN.search(raw_source)), 

104 )