1212# See the License for the specific language governing permissions and
1313# limitations under the License.
1414
15+ import functools
16+ import logging
1517import sys
1618import time
17- import logging
18- import functools
1919
2020from newrelic .api .application import application_instance
21- from newrelic .api .transaction import current_transaction
22- from newrelic .api .time_trace import notice_error
23- from newrelic .api .web_transaction import WSGIWebTransaction
2421from newrelic .api .function_trace import FunctionTrace
2522from newrelic .api .html_insertion import insert_html_snippet , verify_body_exists
26-
23+ from newrelic .api .time_trace import notice_error
24+ from newrelic .api .transaction import current_transaction
25+ from newrelic .api .web_transaction import WSGIWebTransaction
2726from newrelic .common .object_names import callable_name
28- from newrelic .common .object_wrapper import wrap_object , FunctionWrapper
29-
27+ from newrelic .common .object_wrapper import FunctionWrapper , wrap_object
3028from newrelic .packages import six
3129
3230_logger = logging .getLogger (__name__ )
3331
3432
3533class _WSGIApplicationIterable (object ):
36-
3734 def __init__ (self , transaction , generator ):
3835 self .transaction = transaction
3936 self .generator = generator
@@ -68,8 +65,7 @@ def start_trace(self):
6865 self .transaction ._sent_start = time .time ()
6966
7067 if not self .response_trace :
71- self .response_trace = FunctionTrace (
72- name = 'Response' , group = 'Python/WSGI' )
68+ self .response_trace = FunctionTrace (name = "Response" , group = "Python/WSGI" )
7369 self .response_trace .__enter__ ()
7470
7571 def close (self ):
@@ -81,13 +77,12 @@ def close(self):
8177 self .response_trace = None
8278
8379 try :
84- with FunctionTrace (
85- name = 'Finalize' , group = 'Python/WSGI' ):
80+ with FunctionTrace (name = "Finalize" , group = "Python/WSGI" ):
8681
8782 if isinstance (self .generator , _WSGIApplicationMiddleware ):
8883 self .generator .close ()
8984
90- elif hasattr (self .generator , ' close' ):
85+ elif hasattr (self .generator , " close" ):
9186 name = callable_name (self .generator .close )
9287 with FunctionTrace (name ):
9388 self .generator .close ()
@@ -105,7 +100,6 @@ def close(self):
105100
106101
107102class _WSGIInputWrapper (object ):
108-
109103 def __init__ (self , transaction , input ):
110104 self .__transaction = transaction
111105 self .__input = input
@@ -114,7 +108,7 @@ def __getattr__(self, name):
114108 return getattr (self .__input , name )
115109
116110 def close (self ):
117- if hasattr (self .__input , ' close' ):
111+ if hasattr (self .__input , " close" ):
118112 self .__input .close ()
119113
120114 def read (self , * args , ** kwargs ):
@@ -204,8 +198,7 @@ def __init__(self, application, environ, start_response, transaction):
204198
205199 # Grab the iterable returned by the wrapped WSGI
206200 # application.
207- self .iterable = self .application (self .request_environ ,
208- self .start_response )
201+ self .iterable = self .application (self .request_environ , self .start_response )
209202
210203 def process_data (self , data ):
211204 # If this is the first data block, then immediately try
@@ -217,7 +210,7 @@ def html_to_be_inserted():
217210 header = self .transaction .browser_timing_header ()
218211
219212 if not header :
220- return b''
213+ return b""
221214
222215 footer = self .transaction .browser_timing_footer ()
223216
@@ -228,10 +221,12 @@ def html_to_be_inserted():
228221
229222 if modified is not None :
230223 if self .debug :
231- _logger .debug ('RUM insertion from WSGI middleware '
232- 'triggered on first yielded string from '
233- 'response. Bytes added was %r.' ,
234- len (modified ) - len (data ))
224+ _logger .debug (
225+ "RUM insertion from WSGI middleware "
226+ "triggered on first yielded string from "
227+ "response. Bytes added was %r." ,
228+ len (modified ) - len (data ),
229+ )
235230
236231 if self .content_length is not None :
237232 length = len (modified ) - len (data )
@@ -264,7 +259,7 @@ def html_to_be_inserted():
264259
265260 if self .response_data :
266261 self .response_data .append (data )
267- data = b'' .join (self .response_data )
262+ data = b"" .join (self .response_data )
268263 self .response_data = []
269264
270265 # Perform the insertion of the HTML. This should always
@@ -276,10 +271,12 @@ def html_to_be_inserted():
276271
277272 if modified is not None :
278273 if self .debug :
279- _logger .debug ('RUM insertion from WSGI middleware '
280- 'triggered on subsequent string yielded from '
281- 'response. Bytes added was %r.' ,
282- len (modified ) - len (data ))
274+ _logger .debug (
275+ "RUM insertion from WSGI middleware "
276+ "triggered on subsequent string yielded from "
277+ "response. Bytes added was %r." ,
278+ len (modified ) - len (data ),
279+ )
283280
284281 if self .content_length is not None :
285282 length = len (modified ) - len (data )
@@ -297,11 +294,10 @@ def flush_headers(self):
297294 # additional data was inserted into the response.
298295
299296 if self .content_length is not None :
300- header = (( ' Content-Length' , str (self .content_length ) ))
297+ header = (" Content-Length" , str (self .content_length ))
301298 self .response_headers .append (header )
302299
303- self .outer_write = self .outer_start_response (self .response_status ,
304- self .response_headers , * self .response_args )
300+ self .outer_write = self .outer_start_response (self .response_status , self .response_headers , * self .response_args )
305301
306302 def inner_write (self , data ):
307303 # If the write() callable is used, we do not attempt to
@@ -345,8 +341,7 @@ def start_response(self, status, response_headers, *args):
345341 # This is because it can be disabled using an API call.
346342 # Also check whether RUM insertion has already occurred.
347343
348- if (self .transaction .autorum_disabled or
349- self .transaction .rum_header_generated ):
344+ if self .transaction .autorum_disabled or self .transaction .rum_header_generated :
350345
351346 self .flush_headers ()
352347 self .pass_through = True
@@ -370,21 +365,21 @@ def start_response(self, status, response_headers, *args):
370365 for (name , value ) in response_headers :
371366 _name = name .lower ()
372367
373- if _name == ' content-length' :
368+ if _name == " content-length" :
374369 try :
375370 content_length = int (value )
376371 continue
377372
378373 except ValueError :
379374 pass_through = True
380375
381- elif _name == ' content-type' :
376+ elif _name == " content-type" :
382377 content_type = value
383378
384- elif _name == ' content-encoding' :
379+ elif _name == " content-encoding" :
385380 content_encoding = value
386381
387- elif _name == ' content-disposition' :
382+ elif _name == " content-disposition" :
388383 content_disposition = value
389384
390385 headers .append ((name , value ))
@@ -408,9 +403,7 @@ def should_insert_html():
408403
409404 return False
410405
411- if (content_disposition is not None and
412- content_disposition .split (';' )[0 ].strip ().lower () ==
413- 'attachment' ):
406+ if content_disposition is not None and content_disposition .split (";" )[0 ].strip ().lower () == "attachment" :
414407 return False
415408
416409 if content_type is None :
@@ -419,7 +412,7 @@ def should_insert_html():
419412 settings = self .transaction .settings
420413 allowed_content_type = settings .browser_monitoring .content_type
421414
422- if content_type .split (';' )[0 ] not in allowed_content_type :
415+ if content_type .split (";" )[0 ] not in allowed_content_type :
423416 return False
424417
425418 return True
@@ -443,7 +436,7 @@ def close(self):
443436 # Call close() on the iterable as required by the
444437 # WSGI specification.
445438
446- if hasattr (self .iterable , ' close' ):
439+ if hasattr (self .iterable , " close" ):
447440 name = callable_name (self .iterable .close )
448441 with FunctionTrace (name ):
449442 self .iterable .close ()
@@ -518,18 +511,35 @@ def __iter__(self):
518511 yield data
519512
520513
521- def WSGIApplicationWrapper (wrapped , application = None , name = None ,
522- group = None , framework = None ):
514+ def WSGIApplicationWrapper (wrapped , application = None , name = None , group = None , framework = None ):
515+
516+ # Python 2 does not allow rebinding nonlocal variables, so to fix this
517+ # framework must be stored in list so it can be edited by closure.
518+ _framework = [framework ]
519+
520+ def get_framework ():
521+ """Used to delay imports by passing framework as a callable."""
522+ framework = _framework [0 ]
523+ if isinstance (framework , tuple ) or framework is None :
524+ return framework
525+
526+ if callable (framework ):
527+ framework = framework ()
528+ _framework [0 ] = framework
529+
530+ if framework is not None and not isinstance (framework , tuple ):
531+ framework = (framework , None )
532+ _framework [0 ] = framework
523533
524- if framework is not None and not isinstance (framework , tuple ):
525- framework = (framework , None )
534+ return framework
526535
527536 def _nr_wsgi_application_wrapper_ (wrapped , instance , args , kwargs ):
528537 # Check to see if any transaction is present, even an inactive
529538 # one which has been marked to be ignored or which has been
530539 # stopped already.
531540
532541 transaction = current_transaction (active_only = False )
542+ framework = get_framework ()
533543
534544 if transaction :
535545 # If there is any active transaction we will return without
@@ -545,8 +555,7 @@ def _nr_wsgi_application_wrapper_(wrapped, instance, args, kwargs):
545555 # supportability metrics.
546556
547557 if framework :
548- transaction .add_framework_info (
549- name = framework [0 ], version = framework [1 ])
558+ transaction .add_framework_info (name = framework [0 ], version = framework [1 ])
550559
551560 # Also override the web transaction name to be the name of
552561 # the wrapped callable if not explicitly named, and we want
@@ -560,9 +569,8 @@ def _nr_wsgi_application_wrapper_(wrapped, instance, args, kwargs):
560569 if name is None and settings :
561570 if framework is not None :
562571 naming_scheme = settings .transaction_name .naming_scheme
563- if naming_scheme in (None , 'framework' ):
564- transaction .set_transaction_name (
565- callable_name (wrapped ), priority = 1 )
572+ if naming_scheme in (None , "framework" ):
573+ transaction .set_transaction_name (callable_name (wrapped ), priority = 1 )
566574
567575 elif name :
568576 transaction .set_transaction_name (name , group , priority = 1 )
@@ -580,11 +588,11 @@ def _args(environ, start_response, *args, **kwargs):
580588
581589 target_application = application
582590
583- if ' newrelic.app_name' in environ :
584- app_name = environ [' newrelic.app_name' ]
591+ if " newrelic.app_name" in environ :
592+ app_name = environ [" newrelic.app_name" ]
585593
586- if ';' in app_name :
587- app_names = [n .strip () for n in app_name .split (';' )]
594+ if ";" in app_name :
595+ app_names = [n .strip () for n in app_name .split (";" )]
588596 app_name = app_names [0 ]
589597 target_application = application_instance (app_name )
590598 for altname in app_names [1 :]:
@@ -598,7 +606,7 @@ def _args(environ, start_response, *args, **kwargs):
598606
599607 # FIXME Should this allow for multiple apps if a string.
600608
601- if not hasattr (application , ' activate' ):
609+ if not hasattr (application , " activate" ):
602610 target_application = application_instance (application )
603611
604612 # Now start recording the actual web transaction.
@@ -609,8 +617,7 @@ def _args(environ, start_response, *args, **kwargs):
609617 # reporting as supportability metrics.
610618
611619 if framework :
612- transaction .add_framework_info (
613- name = framework [0 ], version = framework [1 ])
620+ transaction .add_framework_info (name = framework [0 ], version = framework [1 ])
614621
615622 # Override the initial web transaction name to be the supplied
616623 # name, or the name of the wrapped callable if wanting to use
@@ -630,24 +637,20 @@ def _args(environ, start_response, *args, **kwargs):
630637 naming_scheme = settings .transaction_name .naming_scheme
631638
632639 if framework is not None :
633- if naming_scheme in (None , 'framework' ):
634- transaction .set_transaction_name (
635- callable_name (wrapped ), priority = 1 )
640+ if naming_scheme in (None , "framework" ):
641+ transaction .set_transaction_name (callable_name (wrapped ), priority = 1 )
636642
637- elif naming_scheme in ('component' , 'framework' ):
638- transaction .set_transaction_name (
639- callable_name (wrapped ), priority = 1 )
643+ elif naming_scheme in ("component" , "framework" ):
644+ transaction .set_transaction_name (callable_name (wrapped ), priority = 1 )
640645
641646 elif name :
642647 transaction .set_transaction_name (name , group , priority = 1 )
643648
644649 def _start_response (status , response_headers , * args ):
645650
646- additional_headers = transaction .process_response (
647- status , response_headers , * args )
651+ additional_headers = transaction .process_response (status , response_headers , * args )
648652
649- _write = start_response (status ,
650- response_headers + additional_headers , * args )
653+ _write = start_response (status , response_headers + additional_headers , * args )
651654
652655 def write (data ):
653656 if not transaction ._sent_start :
@@ -667,17 +670,13 @@ def write(data):
667670 # Should always exist, but check as test harnesses may not
668671 # have it.
669672
670- if 'wsgi.input' in environ :
671- environ ['wsgi.input' ] = _WSGIInputWrapper (transaction ,
672- environ ['wsgi.input' ])
673+ if "wsgi.input" in environ :
674+ environ ["wsgi.input" ] = _WSGIInputWrapper (transaction , environ ["wsgi.input" ])
673675
674- with FunctionTrace (
675- name = 'Application' , group = 'Python/WSGI' ):
676+ with FunctionTrace (name = "Application" , group = "Python/WSGI" ):
676677 with FunctionTrace (name = callable_name (wrapped )):
677- if (settings and settings .browser_monitoring .enabled and
678- not transaction .autorum_disabled ):
679- result = _WSGIApplicationMiddleware (wrapped ,
680- environ , _start_response , transaction )
678+ if settings and settings .browser_monitoring .enabled and not transaction .autorum_disabled :
679+ result = _WSGIApplicationMiddleware (wrapped , environ , _start_response , transaction )
681680 else :
682681 result = wrapped (environ , _start_response )
683682
@@ -691,11 +690,10 @@ def write(data):
691690
692691
693692def wsgi_application (application = None , name = None , group = None , framework = None ):
694- return functools .partial (WSGIApplicationWrapper , application = application ,
695- name = name , group = group , framework = framework )
693+ return functools .partial (
694+ WSGIApplicationWrapper , application = application , name = name , group = group , framework = framework
695+ )
696696
697697
698- def wrap_wsgi_application (module , object_path , application = None ,
699- name = None , group = None , framework = None ):
700- wrap_object (module , object_path , WSGIApplicationWrapper ,
701- (application , name , group , framework ))
698+ def wrap_wsgi_application (module , object_path , application = None , name = None , group = None , framework = None ):
699+ wrap_object (module , object_path , WSGIApplicationWrapper , (application , name , group , framework ))
0 commit comments