77from python_minifier import minify
88from python_minifier .transforms .remove_annotations_options import RemoveAnnotationsOptions
99
10- # Python 2.7 compatibility for UTF-8 file writing
11- if sys .version_info [0 ] == 2 :
12- import codecs
13- def open_utf8 (filename , mode ):
14- return codecs .open (filename , mode , encoding = 'utf-8' )
15- else :
16- def open_utf8 (filename , mode ):
17- return open (filename , mode , encoding = 'utf-8' )
1810
19- def safe_stdout_write (text ):
20- """Write text to stdout with proper encoding handling."""
21- try :
22- sys .stdout .write (text )
23- except UnicodeEncodeError :
24- # Fallback: encode to UTF-8 and write to stdout.buffer (Python 3) or sys.stdout (Python 2)
25- if sys .version_info [0 ] >= 3 and hasattr (sys .stdout , 'buffer' ):
26- sys .stdout .buffer .write (text .encode ('utf-8' ))
27- else :
28- # Python 2.7 or no buffer attribute - write UTF-8 encoded bytes
29- sys .stdout .write (text .encode ('utf-8' ))
11+ class MinificationNotBeneficialError (Exception ):
12+ """Raised when minification results in larger output than the original."""
13+ pass
14+
15+ def stdout_write_bytes (data ):
16+ """Write bytes to stdout with proper Python 2.7/3.x compatibility."""
17+ if sys .version_info >= (3 , 0 ):
18+ sys .stdout .buffer .write (data )
19+ else :
20+ sys .stdout .write (data )
3021
3122
3223if sys .version_info >= (3 , 8 ):
@@ -72,12 +63,23 @@ def main():
7263 if len (args .path ) == 1 and args .path [0 ] == '-' :
7364 # minify stdin
7465 source = sys .stdin .buffer .read () if sys .version_info >= (3 , 0 ) else sys .stdin .read ()
75- minified = do_minify (source , 'stdin' , args )
66+ try :
67+ minified = do_minify (source , 'stdin' , args )
68+ except MinificationNotBeneficialError :
69+ # Use original source when minification isn't beneficial
70+ if args .output :
71+ with open (args .output , 'wb' ) as f :
72+ f .write (source )
73+ else :
74+ # Write original source to stdout
75+ stdout_write_bytes (source )
76+ return
77+
7678 if args .output :
77- with open_utf8 (args .output , 'w ' ) as f :
79+ with open (args .output , 'wb ' ) as f :
7880 f .write (minified )
7981 else :
80- safe_stdout_write (minified )
82+ stdout_write_bytes (minified )
8183
8284 else :
8385 # minify source paths
@@ -88,16 +90,30 @@ def main():
8890 with open (path , 'rb' ) as f :
8991 source = f .read ()
9092
91- minified = do_minify (source , path , args )
93+ try :
94+ minified = do_minify (source , path , args )
95+ except MinificationNotBeneficialError :
96+ # Use original source when minification isn't beneficial
97+ if args .in_place :
98+ # File is already the original, no need to write
99+ pass
100+ elif args .output :
101+ # Write original source to output
102+ with open (args .output , 'wb' ) as f :
103+ f .write (source )
104+ else :
105+ # Write original source to stdout
106+ stdout_write_bytes (source )
107+ continue
92108
93109 if args .in_place :
94- with open_utf8 (path , 'w ' ) as f :
110+ with open (path , 'wb ' ) as f :
95111 f .write (minified )
96112 elif args .output :
97- with open_utf8 (args .output , 'w ' ) as f :
113+ with open (args .output , 'wb ' ) as f :
98114 f .write (minified )
99115 else :
100- safe_stdout_write (minified )
116+ stdout_write_bytes (minified )
101117
102118
103119def parse_args ():
@@ -301,6 +317,15 @@ def error(os_error):
301317
302318
303319def do_minify (source , filename , minification_args ):
320+ """Minify Python source code with size-based fallback.
321+
322+ :param bytes source: Source code as bytes (from file 'rb' or stdin.buffer)
323+ :param str filename: Filename for error reporting
324+ :param argparse.Namespace minification_args: CLI arguments for minification options
325+ :returns: Minified source code as UTF-8 bytes
326+ :rtype: bytes
327+ :raises MinificationNotBeneficialError: When minified output is larger than original
328+ """
304329
305330 preserve_globals = []
306331 if minification_args .preserve_globals :
@@ -329,7 +354,7 @@ def do_minify(source, filename, minification_args):
329354 remove_class_attribute_annotations = minification_args .remove_class_attribute_annotations ,
330355 )
331356
332- return minify (
357+ minified_result = minify (
333358 source ,
334359 filename = filename ,
335360 combine_imports = minification_args .combine_imports ,
@@ -351,6 +376,19 @@ def do_minify(source, filename, minification_args):
351376 constant_folding = minification_args .constant_folding
352377 )
353378
379+ # Encode minified result to bytes for comparison and output
380+ minified_bytes = minified_result .encode ('utf-8' )
381+
382+ # Check if environment variable forces minified output
383+ if os .environ .get ('PYMINIFY_FORCE_BEST_EFFORT' ):
384+ return minified_bytes
385+
386+ # Compare byte lengths for accurate size comparison
387+ if len (minified_bytes ) > len (source ):
388+ raise MinificationNotBeneficialError ("Minified output is longer than original" )
389+
390+ return minified_bytes
391+
354392
355393if __name__ == '__main__' :
356394 main ()
0 commit comments