13
13
import sys
14
14
import tokenize
15
15
import types
16
+ from pathlib import Path
16
17
from typing import Dict
17
18
from typing import List
18
19
from typing import Optional
27
28
from _pytest .assertion .util import ( # noqa: F401
28
29
format_explanation as _format_explanation ,
29
30
)
31
+ from _pytest .compat import fspath
30
32
from _pytest .pathlib import fnmatch_ex
31
33
from _pytest .pathlib import PurePath
32
34
33
- # pytest caches rewritten pycs in __pycache__.
35
+ # pytest caches rewritten pycs in pycache dirs
34
36
PYTEST_TAG = "{}-pytest-{}" .format (sys .implementation .cache_tag , version )
35
37
PYC_EXT = ".py" + (__debug__ and "c" or "o" )
36
38
PYC_TAIL = "." + PYTEST_TAG + PYC_EXT
@@ -103,7 +105,7 @@ def create_module(self, spec):
103
105
return None # default behaviour is fine
104
106
105
107
def exec_module (self , module ):
106
- fn = module .__spec__ .origin
108
+ fn = Path ( module .__spec__ .origin )
107
109
state = self .config ._assertstate
108
110
109
111
self ._rewritten_names .add (module .__name__ )
@@ -117,15 +119,15 @@ def exec_module(self, module):
117
119
# cached pyc is always a complete, valid pyc. Operations on it must be
118
120
# atomic. POSIX's atomic rename comes in handy.
119
121
write = not sys .dont_write_bytecode
120
- cache_dir = os . path . join ( os . path . dirname ( fn ), "__pycache__" )
122
+ cache_dir = get_cache_dir ( fn )
121
123
if write :
122
- ok = try_mkdir (cache_dir )
124
+ ok = try_makedirs (cache_dir )
123
125
if not ok :
124
126
write = False
125
- state .trace ("read only directory: {}" .format (os . path . dirname ( fn ) ))
127
+ state .trace ("read only directory: {}" .format (cache_dir ))
126
128
127
- cache_name = os . path . basename ( fn ) [:- 3 ] + PYC_TAIL
128
- pyc = os . path . join ( cache_dir , cache_name )
129
+ cache_name = fn . name [:- 3 ] + PYC_TAIL
130
+ pyc = cache_dir / cache_name
129
131
# Notice that even if we're in a read-only directory, I'm going
130
132
# to check for a cached pyc. This may not be optimal...
131
133
co = _read_pyc (fn , pyc , state .trace )
@@ -139,7 +141,7 @@ def exec_module(self, module):
139
141
finally :
140
142
self ._writing_pyc = False
141
143
else :
142
- state .trace ("found cached rewritten pyc for {!r }" .format (fn ))
144
+ state .trace ("found cached rewritten pyc for {}" .format (fn ))
143
145
exec (co , module .__dict__ )
144
146
145
147
def _early_rewrite_bailout (self , name , state ):
@@ -258,7 +260,7 @@ def _write_pyc(state, co, source_stat, pyc):
258
260
# (C)Python, since these "pycs" should never be seen by builtin
259
261
# import. However, there's little reason deviate.
260
262
try :
261
- with atomicwrites .atomic_write (pyc , mode = "wb" , overwrite = True ) as fp :
263
+ with atomicwrites .atomic_write (fspath ( pyc ) , mode = "wb" , overwrite = True ) as fp :
262
264
fp .write (importlib .util .MAGIC_NUMBER )
263
265
# as of now, bytecode header expects 32-bit numbers for size and mtime (#4903)
264
266
mtime = int (source_stat .st_mtime ) & 0xFFFFFFFF
@@ -269,14 +271,15 @@ def _write_pyc(state, co, source_stat, pyc):
269
271
except EnvironmentError as e :
270
272
state .trace ("error writing pyc file at {}: errno={}" .format (pyc , e .errno ))
271
273
# we ignore any failure to write the cache file
272
- # there are many reasons, permission-denied, __pycache__ being a
274
+ # there are many reasons, permission-denied, pycache dir being a
273
275
# file etc.
274
276
return False
275
277
return True
276
278
277
279
278
280
def _rewrite_test (fn , config ):
279
281
"""read and rewrite *fn* and return the code object."""
282
+ fn = fspath (fn )
280
283
stat = os .stat (fn )
281
284
with open (fn , "rb" ) as f :
282
285
source = f .read ()
@@ -292,12 +295,12 @@ def _read_pyc(source, pyc, trace=lambda x: None):
292
295
Return rewritten code if successful or None if not.
293
296
"""
294
297
try :
295
- fp = open (pyc , "rb" )
298
+ fp = open (fspath ( pyc ) , "rb" )
296
299
except IOError :
297
300
return None
298
301
with fp :
299
302
try :
300
- stat_result = os .stat (source )
303
+ stat_result = os .stat (fspath ( source ) )
301
304
mtime = int (stat_result .st_mtime )
302
305
size = stat_result .st_size
303
306
data = fp .read (12 )
@@ -749,7 +752,7 @@ def visit_Assert(self, assert_):
749
752
"assertion is always true, perhaps remove parentheses?"
750
753
),
751
754
category = None ,
752
- filename = self .module_path ,
755
+ filename = fspath ( self .module_path ) ,
753
756
lineno = assert_ .lineno ,
754
757
)
755
758
@@ -872,7 +875,7 @@ def warn_about_none_ast(self, node, module_path, lineno):
872
875
lineno={lineno},
873
876
)
874
877
""" .format (
875
- filename = module_path , lineno = lineno
878
+ filename = fspath ( module_path ) , lineno = lineno
876
879
)
877
880
).body
878
881
return ast .If (val_is_none , send_warning , [])
@@ -1018,18 +1021,15 @@ def visit_Compare(self, comp: ast.Compare):
1018
1021
return res , self .explanation_param (self .pop_format_context (expl_call ))
1019
1022
1020
1023
1021
- def try_mkdir (cache_dir ):
1022
- """Attempts to create the given directory, returns True if successful"""
1024
+ def try_makedirs (cache_dir ) -> bool :
1025
+ """Attempts to create the given directory and sub-directories exist, returns True if
1026
+ successful or it already exists"""
1023
1027
try :
1024
- os .mkdir (cache_dir )
1025
- except FileExistsError :
1026
- # Either the __pycache__ directory already exists (the
1027
- # common case) or it's blocked by a non-dir node. In the
1028
- # latter case, we'll ignore it in _write_pyc.
1029
- return True
1030
- except (FileNotFoundError , NotADirectoryError ):
1031
- # One of the path components was not a directory, likely
1032
- # because we're in a zip file.
1028
+ os .makedirs (fspath (cache_dir ), exist_ok = True )
1029
+ except (FileNotFoundError , NotADirectoryError , FileExistsError ):
1030
+ # One of the path components was not a directory:
1031
+ # - we're in a zip file
1032
+ # - it is a file
1033
1033
return False
1034
1034
except PermissionError :
1035
1035
return False
@@ -1039,3 +1039,17 @@ def try_mkdir(cache_dir):
1039
1039
return False
1040
1040
raise
1041
1041
return True
1042
+
1043
+
1044
+ def get_cache_dir (file_path : Path ) -> Path :
1045
+ """Returns the cache directory to write .pyc files for the given .py file path"""
1046
+ if sys .version_info >= (3 , 8 ) and sys .pycache_prefix :
1047
+ # given:
1048
+ # prefix = '/tmp/pycs'
1049
+ # path = '/home/user/proj/test_app.py'
1050
+ # we want:
1051
+ # '/tmp/pycs/home/user/proj'
1052
+ return Path (sys .pycache_prefix ) / Path (* file_path .parts [1 :- 1 ])
1053
+ else :
1054
+ # classic pycache directory
1055
+ return file_path .parent / "__pycache__"
0 commit comments