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