forked from grisha/mod_python
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathimporter.py
More file actions
1946 lines (1426 loc) · 63 KB
/
Copy pathimporter.py
File metadata and controls
1946 lines (1426 loc) · 63 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# vim: set sw=4 expandtab :
#
# Copyright 2004 Apache Software Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License"); you
# may not use this file except in compliance with the License. You
# may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied. See the License for the specific language governing
# permissions and limitations under the License.
#
# Originally developed by Gregory Trubetskoy.
#
# The code in this file originally donated by Graham Dumpleton.
#
# $Id$
from mod_python import apache
from mod_python import publisher
import os
import sys
import new
import types
import pdb
import imp
import md5
import time
import string
import StringIO
import traceback
import cgi
try:
import threading
except:
import dummy_threading as threading
# Define a transient per request modules cache. This is
# not the same as the true global modules cache used by
# the module importer. Instead, the per request modules
# cache is where references to modules loaded in order
# to satisfy the requirements of a specific request are
# stored for the life of that request. Such a cache is
# required to ensure that two distinct bits of code that
# load the same module do in fact use the same instance
# of the module and that an update of the code for a
# module on disk doesn't cause the latter handler code
# to load its own separate instance. This is in part
# necessary because the module loader does not reload a
# module on top of the old module, but loads the new
# instance into a clean module.
class _module_cache(dict): pass
_request_modules_cache = {}
def _cleanup_request_modules_cache(thread=None):
thread = thread or threading.currentThread()
_request_modules_cache.pop(thread, None)
def _setup_request_modules_cache(req=None):
thread = threading.currentThread()
if not _request_modules_cache.has_key(thread):
_request_modules_cache[thread] = _module_cache()
_request_modules_cache[thread].generation = 0
_request_modules_cache[thread].ctime = 0
if req:
req.register_cleanup(_cleanup_request_modules_cache, thread)
def _get_request_modules_cache():
thread = threading.currentThread()
return _request_modules_cache.get(thread, None)
def request_modules_graph(verbose=0):
output = StringIO.StringIO()
modules = _get_request_modules_cache()
print >> output, 'digraph REQUEST {'
print >> output, 'node [shape=box];'
for module in modules.values():
name = module.__name__
filename = module.__file__
if verbose:
cache = module.__mp_info__.cache
ctime = time.asctime(time.localtime(cache.ctime))
mtime = time.asctime(time.localtime(cache.mtime))
atime = time.asctime(time.localtime(cache.atime))
generation = cache.generation
direct = cache.direct
indirect = cache.indirect
path = module.__mp_path__
message = '%s [label="%s\\nmtime = %s\\nctime = %s\\natime = %s\\n'
message += 'generation = %d, direct = %d, indirect = %d\\n'
message += 'path = %s"];'
print >> output, message % (name, filename, mtime, ctime, atime, \
generation, direct, indirect, path)
else:
message = '%s [label="%s"];'
print >> output, message % (name, filename)
children = module.__mp_info__.children
for child_name in children:
print >> output, '%s -> %s' % (name, child_name)
print >> output, '}'
return output.getvalue()
apache.request_modules_graph = request_modules_graph
# Define a transient per request cache into which
# the currently active configuration and handler root
# directory pertaining to a request is stored. This is
# done so that it can be accessed directly from the
# module importer function to obtain configuration
# settings indicating if logging and module reloading is
# enabled and to determine where to look for modules.
_current_cache = {}
def _setup_current_cache(config, options, directory):
thread = threading.currentThread()
if directory:
directory = os.path.normpath(directory)
cache = _current_cache.get(thread, (None, None, None))
if config is None and options is None:
del _current_cache[thread]
else:
_current_cache[thread] = (config, options, directory)
return cache
def get_current_config():
thread = threading.currentThread()
config, options, directory = _current_cache.get(thread,
(apache.main_server.get_config(), None, None))
return config
def get_current_options():
thread = threading.currentThread()
config, options, directory = _current_cache.get(thread,
(None, apache.main_server.get_options(), None))
return options
def get_handler_root():
thread = threading.currentThread()
config, options, directory = _current_cache.get(thread,
(None, None, None))
return directory
apache.get_current_config = get_current_config
apache.get_current_options = get_current_options
apache.get_handler_root = get_handler_root
# Define an alternate implementation of the module
# importer system and substitute it for the standard one
# in the 'mod_python.apache' module.
apache.ximport_module = apache.import_module
def _parent_context():
# Determine the enclosing module which has called
# this function. From that module, return the info
# stashed in it by the module importer system.
try:
raise Exception
except:
parent = sys.exc_info()[2].tb_frame.f_back
while (parent and parent.f_globals.has_key('__file__') and
parent.f_globals['__file__'] == __file__):
parent = parent.f_back
if parent and parent.f_globals.has_key('__mp_info__'):
parent_info = parent.f_globals['__mp_info__']
parent_path = parent.f_globals['__mp_path__']
return (parent_info, parent_path)
return (None, None)
def _find_module(module_name, path):
# Search the specified path for a Python code module
# of the specified name. Note that only Python code
# files with a '.py' extension will be used. Python
# packages will be ignored.
for directory in path:
if directory is not None:
if directory == '~':
root = get_handler_root()
if root is not None:
directory = root
elif directory[:2] == '~/':
root = get_handler_root()
if root is not None:
directory = os.path.join(root, directory[2:])
file = os.path.join(directory, module_name) + '.py'
if os.path.exists(file):
return file
def import_module(module_name, autoreload=None, log=None, path=None):
file = None
import_path = []
# Deal with explicit references to a code file.
# Allow some shortcuts for referring to code file
# relative to handler root or directory of parent
# module. Those relative to parent module are not
# allowed if parent module not imported using this
# module importing system.
if os.path.isabs(module_name):
file = module_name
elif module_name[:2] == '~/':
directory = get_handler_root()
if directory is not None:
file = os.path.join(directory, module_name[2:])
elif module_name[:2] == './':
(parent_info, parent_path) = _parent_context()
if parent_info is not None:
directory = os.path.dirname(parent_info.file)
file = os.path.join(directory, module_name[2:])
elif module_name[:3] == '../':
(parent_info, parent_path) = _parent_context()
if parent_info is not None:
directory = os.path.dirname(parent_info.file)
file = os.path.join(directory, module_name)
if file is None:
# If not an explicit file reference, it is a
# module name. Determine the list of directories
# that need to be searched for a module code
# file. These directories will be, the directory
# of the parent if also imported using this
# importer and any specified search path.
search_path = []
if path is not None:
search_path.extend(path)
(parent_info, parent_path) = _parent_context()
if parent_info is not None:
directory = os.path.dirname(parent_info.file)
search_path.append(directory)
if parent_path is not None:
search_path.extend(parent_path)
options = get_current_options()
if options.has_key('mod_python.importer.path'):
directory = eval(options['mod_python.importer.path'])
search_path.extend(directory)
# Attempt to find the code file for the module
# if we have directories to actually search.
if search_path:
file = _find_module(module_name, search_path)
else:
# For module imported using explicit path, the
# path argument becomes the special embedded
# search path for 'import' statement executed
# within that module.
if path is not None:
import_path = path
# Was a Python code file able to be identified.
if file is not None:
# Use the module importing and caching system
# to load the code from the specified file.
return _global_modules_cache.import_module(file, autoreload, \
log, import_path)
else:
# If a module code file could not be found,
# defer to the standard Python module importer.
# We should always end up here if the request
# was for a package.
return __import__(module_name, {}, {}, ['*'])
apache.import_module = import_module
class _CacheInfo:
def __init__(self, label, file, mtime):
self.label = label
self.file = file
self.mtime = mtime
self.module = None
self.instance = 0
self.generation = 0
self.children = {}
self.path = []
self.atime = 0
self.ctime = 0
self.direct = 0
self.indirect = 0
self.reload = 0
self.lock = threading.Lock()
class _InstanceInfo:
def __init__(self, label, file, cache):
self.label = label
self.file = file
self.cache = cache
self.children = {}
class _ModuleCache:
_prefix = "_mp_"
def __init__(self):
self._cache = {}
self._lock1 = threading.Lock()
self._lock2 = threading.Lock()
self._generation = 0
self._frozen = False
self._directories = {}
def _log_notice(self, msg):
pid = os.getpid()
name = apache.interpreter
flags = apache.APLOG_NOERRNO|apache.APLOG_NOTICE
text = "mod_python (pid=%d, interpreter=%s): %s" % (pid, `name`, msg)
apache.main_server.log_error(text, flags)
def _log_warning(self, msg):
pid = os.getpid()
name = apache.interpreter
flags = apache.APLOG_NOERRNO|apache.APLOG_WARNING
text = "mod_python (pid=%d, interpreter=%s): %s" % (pid, `name`, msg)
apache.main_server.log_error(text, flags)
def _log_exception(self):
pid = os.getpid()
name = apache.interpreter
flags = apache.APLOG_NOERRNO|apache.APLOG_ERR
msg = 'Application error'
text = "mod_python (pid=%d, interpreter=%s): %s" % (pid, `name`, msg)
apache.main_server.log_error(text, flags)
etype, evalue, etb = sys.exc_info()
for text in traceback.format_exception(etype, evalue, etb):
apache.main_server.log_error(text[:-1], flags)
etb = None
def cached_modules(self):
self._lock1.acquire()
try:
return self._cache.keys()
finally:
self._lock1.release()
def module_info(self, label):
self._lock1.acquire()
try:
return self._cache[label]
finally:
self._lock1.release()
def freeze_modules(self):
self._frozen = True
def modules_graph(self, verbose=0):
self._lock1.acquire()
try:
output = StringIO.StringIO()
modules = self._cache
print >> output, 'digraph GLOBAL {'
print >> output, 'node [shape=box];'
for cache in modules.values():
name = cache.label
filename = cache.file
if verbose:
ctime = time.asctime(time.localtime(cache.ctime))
mtime = time.asctime(time.localtime(cache.mtime))
atime = time.asctime(time.localtime(cache.atime))
generation = cache.generation
direct = cache.direct
indirect = cache.indirect
path = cache.path
message = '%s [label="%s\\nmtime = %s\\nctime = %s\\n'
message += 'atime = %s\\ngeneration = %d, direct = %d,'
message += 'indirect = %d\\npath = %s"];'
print >> output, message % (name, filename, mtime, ctime,
atime, generation, direct, indirect, path)
else:
message = '%s [label="%s"];'
print >> output, message % (name, filename)
children = cache.children
for child_name in children:
print >> output, '%s -> %s' % (name, child_name)
print >> output, '}'
return output.getvalue()
finally:
self._lock1.release()
def _check_directory(self, file):
directory = os.path.dirname(file)
if not directory in self._directories:
self._directories[directory] = None
if directory in sys.path:
msg = 'Module directory listed in "sys.path". '
msg = msg + 'This may cause problems. Please check code. '
msg = msg + 'File being imported is "%s".' % file
self._log_warning(msg)
def import_module(self, file, autoreload=None, log=None, path=None):
# Ensure that file name is normalised so all
# lookups against the cache equate where they
# are the same file. This isn't necessarily
# going to work where symlinks are involved, but
# not much else that can be done in that case.
file = os.path.normpath(file)
# Determine the default values for the module
# autoreloading and logging arguments direct
# from the Apache configuration rather than
# having fixed defaults.
if autoreload is None or log is None:
config = get_current_config()
if autoreload is None:
autoreload = int(config.get("PythonAutoReload", 1))
if log is None:
log = int(config.get("PythonDebug", 0))
# Warn of any instances where a code file is
# imported from a directory which also appears
# in 'sys.path'.
if log:
self._check_directory(file)
# Retrieve the parent context. That is, the
# details stashed into the parent module by the
# module importing system itself.
(parent_info, parent_path) = _parent_context()
# Check for an attempt by the module to import
# itself.
if parent_info:
assert(file != parent_info.file), "Import cycle in %s." % file
# Retrieve the per request modules cache entry.
modules = _get_request_modules_cache()
# Calculate a unique label corresponding to the
# name of the file which is the module. This
# will be used as the '__name__' attribute of a
# module and as key in various tables.
label = self._module_label(file)
# See if the requested module has already been
# imported previously within the context of this
# request or at least visited by way of prior
# dependency checks where it was deemed that it
# didn't need to be reloaded. If it has we can
# skip any additional dependency checks and use
# the module already identified. This ensures
# the same actual module instance is used. This
# check is also required so that we don't get
# into cyclical import loops. Still need to at
# least record the fact that the module is a
# child of the parent.
if modules is not None:
if modules.has_key(label):
if parent_info:
parent_info.children[label] = time.time()
return modules[label]
# Now move on to trying to find the actual
# module.
try:
cache = None
# First determine if the module has been loaded
# previously. If not already loaded or if a
# dependency of the module has been changed on disk
# or reloaded since parent was loaded, must load the
# module.
(cache, load) = self._reload_required(modules,
label, file, autoreload)
# Make sure that the cache entry is locked by the
# thread so that other threads in a multithreaded
# system don't try and load the same module at the
# same time.
cache.lock.acquire()
# If this per request modules cache has just
# been created for the first time, record some
# details in it about current cache state and
# run time of the request.
if modules.ctime == 0:
modules.generation = self._generation
modules.ctime = time.time()
# Import module or obtain it from cache as is
# appropriate.
if load:
# Setup a new empty module to load the code for
# the module into. Increment the instance count
# and set the reload flag to force a reload if
# the import fails.
cache.instance = cache.instance + 1
cache.reload = 1
module = imp.new_module(label)
# If the module was previously loaded we need to
# manage the transition to the new instance of
# the module that is being loaded to replace it.
# This entails calling the special clone method,
# if provided within the existing module. Using
# this method the existing module can then
# selectively indicate what should be transfered
# over to the next instance of the module,
# including thread locks. If this process fails
# the special purge method is called, if
# provided, to indicate that the existing module
# is being forcibly purged out of the system. In
# that case any existing state will not be
# transferred.
if cache.module != None:
if hasattr(cache.module, "__mp_clone__"):
try:
# Migrate any existing state data from
# existing module instance to new module
# instance.
if log:
msg = "Cloning module '%s'" % file
self._log_notice(msg)
cache.module.__mp_clone__(module)
except:
# Forcibly purging module from system.
self._log_exception()
if log:
msg = "Purging module '%s'" % file
self._log_notice(msg)
if hasattr(cache.module, "__mp_purge__"):
try:
cache.module.__mp_purge__()
except:
self._log_exception()
cache.module = None
# Setup a fresh new module yet again.
module = imp.new_module(label)
if log:
if cache.module == None:
msg = "Importing module '%s'" % file
self._log_notice(msg)
else:
msg = "Reimporting module '%s'" % file
self._log_notice(msg)
else:
if log:
msg = "Importing module '%s'" % file
self._log_notice(msg)
# Must add to the module the path to the modules
# file. This ensures that module looks like a
# normal module and this path will also be used
# in certain contexts when the import statement
# is used within the module.
module.__file__ = file
# Setup a new instance object to store in the
# module. This will refer back to the actual
# cache entry and is used to record information
# which is specific to this incarnation of the
# module when reloading is occuring.
instance = _InstanceInfo(label, file, cache)
module.__mp_info__ = instance
# Cache any additional module search path which
# should be used for this instance of the module
# or package. The path shouldn't be able to be
# changed during the lifetime of the module to
# ensure that module imports are always done
# against the same path.
if path is None:
path = []
module.__mp_path__ = list(path)
# Place a reference to the module within the
# request specific cache of imported modules.
# This makes module lookup more efficient when
# the same module is imported more than once
# within the context of a request. In the case
# of a cyclical import, avoids a never ending
# recursive loop.
if modules is not None:
modules[label] = module
# If this is a child import of some parent
# module, add this module as a child of the
# parent.
atime = time.time()
if parent_info:
parent_info.children[label] = atime
# Perform actual import of the module.
try:
execfile(file, module.__dict__)
except:
# Importation of the module has failed for
# some reason. If this is the very first
# import of the module, need to discard the
# cache entry entirely else a subsequent
# attempt to load the module will wrongly
# think it was successfully loaded already.
if cache.module is None:
del self._cache[label]
raise
# Update the cache and clear the reload flag.
cache.module = module
cache.reload = 0
# Need to also update the list of child modules
# stored in the cache entry with the actual
# children found during the import. A copy is
# made, meaning that any future imports
# performed within the context of the request
# handler don't result in the module later being
# reloaded if they change.
cache.children = dict(module.__mp_info__.children)
# Create link to embedded path at end of import.
cache.path = module.__mp_path__
# Increment the generation count of the global
# state of all modules. This is used in the
# dependency management scheme for reloading to
# determine if a module dependency has been
# reloaded since it was loaded.
self._lock2.acquire()
self._generation = self._generation + 1
cache.generation = self._generation
self._lock2.release()
# Update access time and reset access counts.
cache.ctime = atime
cache.atime = atime
cache.direct = 1
cache.indirect = 0
else:
# Update the cache.
module = cache.module
# Place a reference to the module within the
# request specific cache of imported modules.
# This makes module lookup more efficient when
# the same module is imported more than once
# within the context of a request. In the case
# of a cyclical import, avoids a never ending
# recursive loop.
if modules is not None:
modules[label] = module
# If this is a child import of some parent
# module, add this module as a child of the
# parent.
atime = time.time()
if parent_info:
parent_info.children[label] = atime
# Didn't need to reload the module so simply
# increment access counts and last access time.
cache.atime = atime
cache.direct = cache.direct + 1
return module
finally:
# Lock on cache object can now be released.
if cache is not None:
cache.lock.release()
def _reload_required(self, modules, label, file, autoreload):
# Make sure cache lock is always released.
try:
self._lock1.acquire()
# Check if this is a new module.
if not self._cache.has_key(label):
mtime = os.path.getmtime(file)
cache = _CacheInfo(label, file, mtime)
self._cache[label] = cache
return (cache, True)
# Grab entry from cache.
cache = self._cache[label]
# Check if reloads have been disabled.
# Only avoid a reload though if module
# hadn't been explicitly marked to be
# reloaded.
if not cache.reload:
if self._frozen or not autoreload:
return (cache, False)
# Determine modification time of file.
try:
mtime = os.path.getmtime(file)
except:
# Must have been removed just then. We return
# currently cached module and avoid a reload.
# Defunct module would need to be purged later.
msg = 'Module code file has been removed. '
msg = msg + 'This may cause problems. Using cached module. '
msg = msg + 'File being imported "%s".' % file
self._log_warning(msg)
return (cache, False)
# Check if modification time has changed or
# if module has been flagged to be reloaded.
if cache.reload or mtime != cache.mtime:
cache.mtime = mtime
return (cache, True)
# Check if children have changed in any way
# that would require a reload.
if cache.children:
visited = {}
ancestors = [label]
for tag in cache.children:
# If the child isn't in the cache any longer
# for some reason, force a reload.
if not self._cache.has_key(tag):
return (cache, True)
child = self._cache[tag]
# Now check the actual child module.
if self._check_module(modules, cache, child,
visited, ancestors):
return (cache, True)
# No need to reload the module. Module
# should be cached in the request object by
# the caller if required.
return (cache, False)
finally:
self._lock1.release()
def _check_module(self, modules, parent, current, visited, ancestors):
# Update current modules access statistics.
current.indirect = current.indirect + 1
current.atime = time.time()
# Check if current module has been marked
# for reloading.
if current.reload:
return True
# Check if current module has been reloaded
# since the parent was last loaded.
if current.generation > parent.generation:
return True
# If the current module has been visited
# already, no need to continue further as it
# should be up to date.
if visited.has_key(current.label):
return False
# Check if current module has been modified on
# disk since last loaded.
try:
mtime = os.path.getmtime(current.file)
if mtime != current.mtime:
return True
except:
# Current module must have been removed.
# Don't cause this to force a reload though
# as can cause problems. Rely on the parent
# being modified to cause a reload.
msg = 'Module code file has been removed. '
msg = msg + 'This may cause problems. Using cached module. '
msg = msg + 'File being imported "%s".' % current.file
self._log_warning(msg)
if modules is not None:
modules[current.label] = current.module
return False
# Check to see if all the children of the
# current module need updating or are newer than
# the current module.
if current.children:
ancestors = ancestors + [current.label]
for label in current.children.keys():
# Check for a child which refers to one of its
# ancestors. Hopefully this will never occur. If
# it does we will force a reload every time to
# highlight there is a problem. Note this does
# not get detected first time module is loaded,
# only here on subsequent checks. If reloading
# is not enabled, then problem will never be
# detected and flagged.
if label in ancestors:
msg = 'Module imports an ancestor module. '
msg = msg + 'This may cause problems. Please check code. '
msg = msg + 'File doing import is "%s".' % current.file
self._log_warning(msg)
return True
# If the child isn't in the cache any longer for
# some reason, force a reload.
if not self._cache.has_key(label):
return True
child = self._cache[label]
# Recurse back into this function to check
# child.
if self._check_module(modules, current, child,
visited, ancestors):
return True
# No need to reload the current module. Now safe
# to mark the current module as having been
# visited and cache it into the request object
# for quick later lookup if a parent needs to be
# reloaded.
visited[current.label] = current
if modules is not None:
modules[current.label] = current.module
return False
def _module_label(self, file):
# The label is used in the __name__ field of the
# module and then used in determining child
# module imports. Thus really needs to be
# unique. We don't really want to use a module
# name which is a filesystem path. Hope MD5 hex
# digest is okay.
return self._prefix + md5.new(file).hexdigest()
_global_modules_cache = _ModuleCache()
def _get_global_modules_cache():
return _global_modules_cache
apache.freeze_modules = _global_modules_cache.freeze_modules
apache.modules_graph = _global_modules_cache.modules_graph
apache.module_info = _global_modules_cache.module_info
class _ModuleLoader:
def __init__(self, file):
self.__file = file
def load_module(self, fullname):
return _global_modules_cache.import_module(self.__file)
class _ModuleImporter:
def find_module(self, fullname, path=None):
# Return straight away if requested to import a
# sub module of a package.
if '.' in fullname: