1313import sys
1414import tokenize
1515import types
16+ from pathlib import Path
1617from typing import Dict
1718from typing import List
1819from typing import Optional
3031from _pytest .pathlib import fnmatch_ex
3132from _pytest .pathlib import PurePath
3233
33- # pytest caches rewritten pycs in __pycache__.
34+ # pytest caches rewritten pycs in pycache dirs
3435PYTEST_TAG = "{}-pytest-{}" .format (sys .implementation .cache_tag , version )
3536PYC_EXT = ".py" + (__debug__ and "c" or "o" )
3637PYC_TAIL = "." + PYTEST_TAG + PYC_EXT
@@ -103,7 +104,7 @@ def create_module(self, spec):
103104 return None # default behaviour is fine
104105
105106 def exec_module (self , module ):
106- fn = module .__spec__ .origin
107+ fn = Path ( module .__spec__ .origin )
107108 state = self .config ._assertstate
108109
109110 self ._rewritten_names .add (module .__name__ )
@@ -117,15 +118,15 @@ def exec_module(self, module):
117118 # cached pyc is always a complete, valid pyc. Operations on it must be
118119 # atomic. POSIX's atomic rename comes in handy.
119120 write = not sys .dont_write_bytecode
120- cache_dir = os . path . join ( os . path . dirname ( fn ), "__pycache__" )
121+ cache_dir = get_cache_dir ( fn )
121122 if write :
122123 ok = try_mkdir (cache_dir )
123124 if not ok :
124125 write = False
125- state .trace ("read only directory: {}" .format (os . path . dirname ( fn ) ))
126+ state .trace ("read only directory: {}" .format (cache_dir ))
126127
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
129130 # Notice that even if we're in a read-only directory, I'm going
130131 # to check for a cached pyc. This may not be optimal...
131132 co = _read_pyc (fn , pyc , state .trace )
@@ -139,7 +140,7 @@ def exec_module(self, module):
139140 finally :
140141 self ._writing_pyc = False
141142 else :
142- state .trace ("found cached rewritten pyc for {!r }" .format (fn ))
143+ state .trace ("found cached rewritten pyc for {}" .format (fn ))
143144 exec (co , module .__dict__ )
144145
145146 def _early_rewrite_bailout (self , name , state ):
@@ -258,7 +259,7 @@ def _write_pyc(state, co, source_stat, pyc):
258259 # (C)Python, since these "pycs" should never be seen by builtin
259260 # import. However, there's little reason deviate.
260261 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 :
262263 fp .write (importlib .util .MAGIC_NUMBER )
263264 # as of now, bytecode header expects 32-bit numbers for size and mtime (#4903)
264265 mtime = int (source_stat .st_mtime ) & 0xFFFFFFFF
@@ -269,14 +270,15 @@ def _write_pyc(state, co, source_stat, pyc):
269270 except EnvironmentError as e :
270271 state .trace ("error writing pyc file at {}: errno={}" .format (pyc , e .errno ))
271272 # 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
273274 # file etc.
274275 return False
275276 return True
276277
277278
278279def _rewrite_test (fn , config ):
279280 """read and rewrite *fn* and return the code object."""
281+ fn = str (fn )
280282 stat = os .stat (fn )
281283 with open (fn , "rb" ) as f :
282284 source = f .read ()
@@ -292,12 +294,12 @@ def _read_pyc(source, pyc, trace=lambda x: None):
292294 Return rewritten code if successful or None if not.
293295 """
294296 try :
295- fp = open (pyc , "rb" )
297+ fp = open (str ( pyc ) , "rb" )
296298 except IOError :
297299 return None
298300 with fp :
299301 try :
300- stat_result = os .stat (source )
302+ stat_result = os .stat (str ( source ) )
301303 mtime = int (stat_result .st_mtime )
302304 size = stat_result .st_size
303305 data = fp .read (12 )
@@ -749,7 +751,7 @@ def visit_Assert(self, assert_):
749751 "assertion is always true, perhaps remove parentheses?"
750752 ),
751753 category = None ,
752- filename = self .module_path ,
754+ filename = str ( self .module_path ) ,
753755 lineno = assert_ .lineno ,
754756 )
755757
@@ -872,7 +874,7 @@ def warn_about_none_ast(self, node, module_path, lineno):
872874 lineno={lineno},
873875)
874876 """ .format (
875- filename = module_path , lineno = lineno
877+ filename = str ( module_path ) , lineno = lineno
876878 )
877879 ).body
878880 return ast .If (val_is_none , send_warning , [])
@@ -1021,9 +1023,9 @@ def visit_Compare(self, comp: ast.Compare):
10211023def try_mkdir (cache_dir ):
10221024 """Attempts to create the given directory, returns True if successful"""
10231025 try :
1024- os .mkdir ( cache_dir )
1026+ os .makedirs ( str ( cache_dir ) )
10251027 except FileExistsError :
1026- # Either the __pycache__ directory already exists (the
1028+ # Either the pycache directory already exists (the
10271029 # common case) or it's blocked by a non-dir node. In the
10281030 # latter case, we'll ignore it in _write_pyc.
10291031 return True
@@ -1039,3 +1041,17 @@ def try_mkdir(cache_dir):
10391041 return False
10401042 raise
10411043 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