5
5
from pathlib import Path
6
6
from typing import TYPE_CHECKING , Any , Protocol
7
7
8
+ from ruamel .yaml import YAML
9
+ from ruamel .yaml .error import YAMLError
10
+
8
11
try :
9
12
import tomllib # type: ignore[import-not-found]
10
13
except ImportError : # Python < 3.11
@@ -128,20 +131,53 @@ def resolve_requirements_file(
128
131
options : dict [str , Any ],
129
132
) -> Path | None :
130
133
requirements_file : Path | None
131
- path_str = options .get ("requirements_file" , "requirements.txt" )
132
- requirements_file = Path (path_str ).resolve ()
133
- if not requirements_file .exists ():
134
- return None
135
- return requirements_file
134
+ path_str = options .get ("requirements_file" )
135
+ if path_str is not None :
136
+ requirements_file = Path (path_str ).resolve ()
137
+ if requirements_file .exists ():
138
+ return requirements_file
139
+
140
+ # Check scrapinghub.yml for requirements file
141
+ scrapinghub_file = Path ("scrapinghub.yml" )
142
+ yaml_parser = YAML (typ = "safe" )
143
+ if scrapinghub_file .exists ():
144
+ try :
145
+ with scrapinghub_file .open (encoding = "utf-8" ) as f :
146
+ data = yaml_parser .load (f )
147
+ except (UnicodeDecodeError , YAMLError ):
148
+ pass
149
+ else :
150
+ try :
151
+ requirements_file_name = data .get ("requirements" , {}).get (
152
+ "file" ,
153
+ "" ,
154
+ )
155
+ except AttributeError :
156
+ pass
157
+ else :
158
+ if requirements_file_name and isinstance (
159
+ requirements_file_name ,
160
+ str ,
161
+ ):
162
+ scrapinghub_requirements_file = Path (requirements_file_name )
163
+ if scrapinghub_requirements_file .exists ():
164
+ return scrapinghub_requirements_file .resolve ()
165
+
166
+ # Fall back to requirements.txt
167
+ requirements_file = Path ("requirements.txt" )
168
+ if requirements_file .exists ():
169
+ return requirements_file .resolve ()
170
+
171
+ return None
136
172
137
173
@classmethod
138
174
def load_options (cls , root : Path ) -> dict [str , Any ]:
139
175
pyproject_path = root / "pyproject.toml"
140
176
if not pyproject_path .exists ():
141
- raise ValueError ( f" { pyproject_path } does not exist." )
177
+ return {}
142
178
try :
143
179
pyproject = tomllib .loads (pyproject_path .read_text (encoding = "utf-8" ))
144
- except tomllib .TOMLDecodeError as e :
180
+ except ( tomllib .TOMLDecodeError , UnicodeDecodeError ) as e :
145
181
raise ValueError (f"Invalid pyproject.toml: { e } " ) from None
146
182
return pyproject .get ("tool" , {}).get ("scrapy-lint" , {})
147
183
@@ -169,17 +205,20 @@ def resolve_files(
169
205
files .add (zyte_config_path )
170
206
if requirements_path and requirements_path .exists ():
171
207
files .add (requirements_path )
172
- for python_file_path in path .glob ("*/**.py" ):
173
- if spec is None or not spec .match_file (python_file_path ):
208
+ for python_file_path in path .glob ("**/*.py" ):
209
+ if spec is None or not spec .match_file (
210
+ python_file_path .relative_to (root ),
211
+ ):
174
212
files .add (python_file_path )
175
213
return sorted (files )
176
214
177
215
def lint (self ) -> Generator [Issue ]:
178
216
for file in self .files :
217
+ absolute_file = file .resolve ()
179
218
for issue in self .lint_file (file ):
180
219
if self .is_ignored (issue , file ):
181
220
continue
182
- issue .file = file .relative_to (self .root )
221
+ issue .file = absolute_file .relative_to (self .root )
183
222
yield issue
184
223
185
224
def is_ignored (self , issue : Issue , file : Path ) -> bool :
@@ -192,15 +231,17 @@ def lint_file(self, file: Path) -> Generator[Issue]:
192
231
yield from self .lint_python_file (file )
193
232
elif file .name == "scrapinghub.yml" :
194
233
yield from ZyteCloudConfigIssueFinder (self .context ).lint (file )
195
- elif file == self .requirements_file :
234
+ elif self . requirements_file is not None and file == self .requirements_file :
196
235
yield from RequirementsIssueFinder (self .context ).lint (file )
197
236
198
237
def lint_python_file (self , file : Path ) -> Generator [Issue ]:
199
238
try :
200
239
with file .open ("r" , encoding = "utf-8" ) as f :
201
240
source = f .read ()
202
241
except UnicodeDecodeError as e :
203
- raise ValueError (f"Could not read { file } : { e } " ) from None
242
+ raise ValueError (
243
+ f"Could not read { file .relative_to (self .root )} : { e } " ,
244
+ ) from None
204
245
tree = ast .parse (source , filename = str (file ))
205
246
setting_module_finder = SettingModuleIssueFinder (
206
247
self .context ,
0 commit comments