1313import sys
1414import tokenize
1515import types
16+ from pathlib import Path
1617from typing import Dict
1718from typing import List
1819from typing import Optional
2728from _pytest .assertion .util import ( # noqa: F401
2829 format_explanation as _format_explanation ,
2930)
31+ from _pytest .compat import fspath
3032from _pytest .pathlib import fnmatch_ex
3133from _pytest .pathlib import PurePath
3234
33- # pytest caches rewritten pycs in __pycache__.
35+ # pytest caches rewritten pycs in pycache dirs
3436PYTEST_TAG = "{}-pytest-{}" .format (sys .implementation .cache_tag , version )
3537PYC_EXT = ".py" + (__debug__ and "c" or "o" )
3638PYC_TAIL = "." + PYTEST_TAG + PYC_EXT
@@ -103,7 +105,7 @@ def create_module(self, spec):
103105 return None # default behaviour is fine
104106
105107 def exec_module (self , module ):
106- fn = module .__spec__ .origin
108+ fn = Path ( module .__spec__ .origin )
107109 state = self .config ._assertstate
108110
109111 self ._rewritten_names .add (module .__name__ )
@@ -117,15 +119,15 @@ def exec_module(self, module):
117119 # cached pyc is always a complete, valid pyc. Operations on it must be
118120 # atomic. POSIX's atomic rename comes in handy.
119121 write = not sys .dont_write_bytecode
120- cache_dir = os . path . join ( os . path . dirname ( fn ), "__pycache__" )
122+ cache_dir = get_cache_dir ( fn )
121123 if write :
122- ok = try_mkdir (cache_dir )
124+ ok = try_makedirs (cache_dir )
123125 if not ok :
124126 write = False
125- state .trace ("read only directory: {}" .format (os . path . dirname ( fn ) ))
127+ state .trace ("read only directory: {}" .format (cache_dir ))
126128
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
129131 # Notice that even if we're in a read-only directory, I'm going
130132 # to check for a cached pyc. This may not be optimal...
131133 co = _read_pyc (fn , pyc , state .trace )
@@ -139,7 +141,7 @@ def exec_module(self, module):
139141 finally :
140142 self ._writing_pyc = False
141143 else :
142- state .trace ("found cached rewritten pyc for {!r }" .format (fn ))
144+ state .trace ("found cached rewritten pyc for {}" .format (fn ))
143145 exec (co , module .__dict__ )
144146
145147 def _early_rewrite_bailout (self , name , state ):
@@ -258,7 +260,7 @@ def _write_pyc(state, co, source_stat, pyc):
258260 # (C)Python, since these "pycs" should never be seen by builtin
259261 # import. However, there's little reason deviate.
260262 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 :
262264 fp .write (importlib .util .MAGIC_NUMBER )
263265 # as of now, bytecode header expects 32-bit numbers for size and mtime (#4903)
264266 mtime = int (source_stat .st_mtime ) & 0xFFFFFFFF
@@ -269,14 +271,15 @@ def _write_pyc(state, co, source_stat, pyc):
269271 except EnvironmentError as e :
270272 state .trace ("error writing pyc file at {}: errno={}" .format (pyc , e .errno ))
271273 # 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
273275 # file etc.
274276 return False
275277 return True
276278
277279
278280def _rewrite_test (fn , config ):
279281 """read and rewrite *fn* and return the code object."""
282+ fn = fspath (fn )
280283 stat = os .stat (fn )
281284 with open (fn , "rb" ) as f :
282285 source = f .read ()
@@ -292,12 +295,12 @@ def _read_pyc(source, pyc, trace=lambda x: None):
292295 Return rewritten code if successful or None if not.
293296 """
294297 try :
295- fp = open (pyc , "rb" )
298+ fp = open (fspath ( pyc ) , "rb" )
296299 except IOError :
297300 return None
298301 with fp :
299302 try :
300- stat_result = os .stat (source )
303+ stat_result = os .stat (fspath ( source ) )
301304 mtime = int (stat_result .st_mtime )
302305 size = stat_result .st_size
303306 data = fp .read (12 )
@@ -749,7 +752,7 @@ def visit_Assert(self, assert_):
749752 "assertion is always true, perhaps remove parentheses?"
750753 ),
751754 category = None ,
752- filename = self .module_path ,
755+ filename = fspath ( self .module_path ) ,
753756 lineno = assert_ .lineno ,
754757 )
755758
@@ -872,7 +875,7 @@ def warn_about_none_ast(self, node, module_path, lineno):
872875 lineno={lineno},
873876)
874877 """ .format (
875- filename = module_path , lineno = lineno
878+ filename = fspath ( module_path ) , lineno = lineno
876879 )
877880 ).body
878881 return ast .If (val_is_none , send_warning , [])
@@ -1018,18 +1021,15 @@ def visit_Compare(self, comp: ast.Compare):
10181021 return res , self .explanation_param (self .pop_format_context (expl_call ))
10191022
10201023
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"""
10231027 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
10331033 return False
10341034 except PermissionError :
10351035 return False
@@ -1039,3 +1039,17 @@ def try_mkdir(cache_dir):
10391039 return False
10401040 raise
10411041 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