1+ #  Copyright 2022 The Matrix.org Foundation C.I.C. 
2+ # 
3+ #  Licensed under the Apache License, Version 2.0 (the "License"); 
4+ #  you may not use this file except in compliance with the License. 
5+ #  You may obtain a copy of the License at 
6+ # 
7+ #      http://www.apache.org/licenses/LICENSE-2.0 
8+ # 
9+ #  Unless required by applicable law or agreed to in writing, software 
10+ #  distributed under the License is distributed on an "AS IS" BASIS, 
11+ #  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
12+ #  See the License for the specific language governing permissions and 
13+ #  limitations under the License. 
14+ # 
15+ 
16+ """ 
17+ This module exposes a single function which checks synapse's dependencies are present 
18+ and correctly versioned. It makes use of `importlib.metadata` to do so. The details 
19+ are a bit murky: there's no easy way to get a map from "extras" to the packages they 
20+ require. But this is probably just symptomatic of Python's package management. 
21+ """ 
22+ 
123import  logging 
224from  typing  import  Iterable , NamedTuple , Optional 
325
1032except  ImportError :
1133    import  importlib_metadata  as  metadata   # type: ignore[no-redef] 
1234
35+ __all__  =  ["check_requirements" ]
36+ 
1337
1438class  DependencyException (Exception ):
1539    @property  
@@ -29,7 +53,17 @@ def dependencies(self) -> Iterable[str]:
2953            yield  '"'  +  i  +  '"' 
3054
3155
32- EXTRAS  =  set (metadata .metadata (DISTRIBUTION_NAME ).get_all ("Provides-Extra" ))
56+ DEV_EXTRAS  =  {"lint" , "mypy" , "test" , "dev" }
57+ RUNTIME_EXTRAS  =  (
58+     set (metadata .metadata (DISTRIBUTION_NAME ).get_all ("Provides-Extra" )) -  DEV_EXTRAS 
59+ )
60+ VERSION  =  metadata .version (DISTRIBUTION_NAME )
61+ 
62+ 
63+ def  _is_dev_dependency (req : Requirement ) ->  bool :
64+     return  req .marker  is  not None  and  any (
65+         req .marker .evaluate ({"extra" : e }) for  e  in  DEV_EXTRAS 
66+     )
3367
3468
3569class  Dependency (NamedTuple ):
@@ -43,6 +77,9 @@ def _generic_dependencies() -> Iterable[Dependency]:
4377    assert  requirements  is  not None 
4478    for  raw_requirement  in  requirements :
4579        req  =  Requirement (raw_requirement )
80+         if  _is_dev_dependency (req ):
81+             continue 
82+ 
4683        # https://packaging.pypa.io/en/latest/markers.html#usage notes that 
4784        #   > Evaluating an extra marker with no environment is an error 
4885        # so we pass in a dummy empty extra value here. 
@@ -56,6 +93,8 @@ def _dependencies_for_extra(extra: str) -> Iterable[Dependency]:
5693    assert  requirements  is  not None 
5794    for  raw_requirement  in  requirements :
5895        req  =  Requirement (raw_requirement )
96+         if  _is_dev_dependency (req ):
97+             continue 
5998        # Exclude mandatory deps by only selecting deps needed with this extra. 
6099        if  (
61100            req .marker  is  not None 
@@ -67,18 +106,26 @@ def _dependencies_for_extra(extra: str) -> Iterable[Dependency]:
67106
68107def  _not_installed (requirement : Requirement , extra : Optional [str ] =  None ) ->  str :
69108    if  extra :
70-         return  f"Need { requirement .name } { extra }  
109+         return  (
110+             f"Synapse { VERSION } { requirement .name } { extra }  
111+             f"but it is not installed" 
112+         )
71113    else :
72-         return  f"Need  { requirement .name }  
114+         return  f"Synapse  { VERSION }  needs  { requirement .name }  
73115
74116
75117def  _incorrect_version (
76118    requirement : Requirement , got : str , extra : Optional [str ] =  None 
77119) ->  str :
78120    if  extra :
79-         return  f"Need { requirement } { extra } { requirement .name } { got }  
121+         return  (
122+             f"Synapse { VERSION } { requirement } { extra }  
123+             f"but got { requirement .name } { got }  
124+         )
80125    else :
81-         return  f"Need { requirement } { requirement .name } { got }  
126+         return  (
127+             f"Synapse { VERSION } { requirement } { requirement .name } { got }  
128+         )
82129
83130
84131def  check_requirements (extra : Optional [str ] =  None ) ->  None :
@@ -100,10 +147,10 @@ def check_requirements(extra: Optional[str] = None) -> None:
100147    # First work out which dependencies are required, and which are optional. 
101148    if  extra  is  None :
102149        dependencies  =  _generic_dependencies ()
103-     elif  extra  in  EXTRAS :
150+     elif  extra  in  RUNTIME_EXTRAS :
104151        dependencies  =  _dependencies_for_extra (extra )
105152    else :
106-         raise  ValueError (f"Synapse does not provide the feature '{ extra }  )
153+         raise  ValueError (f"Synapse { VERSION }   does not provide the feature '{ extra }  )
107154
108155    deps_unfulfilled  =  []
109156    errors  =  []
0 commit comments