Transform Python source code into it's most compact representation

Overview

Python Minifier

python-minifier

Transforms Python source code into it's most compact representation.

Try it out!

python-minifier currently supports Python 2.7 and Python 3.3 to 3.10. Previous releases supported Python 2.6.

As an example, the following python source:

def handler(event, context):
    l.info(event)
    try:
        i_token = hashlib.new('md5', (event['RequestId'] + event['StackId']).encode()).hexdigest()
        props = event['ResourceProperties']

        if event['RequestType'] == 'Create':
            event['PhysicalResourceId'] = 'None'
            event['PhysicalResourceId'] = create_cert(props, i_token)
            add_tags(event['PhysicalResourceId'], props)
            validate(event['PhysicalResourceId'], props)

            if wait_for_issuance(event['PhysicalResourceId'], context):
                event['Status'] = 'SUCCESS'
                return send(event)
            else:
                return reinvoke(event, context)

        elif event['RequestType'] == 'Delete':
            if event['PhysicalResourceId'] != 'None':
                acm.delete_certificate(CertificateArn=event['PhysicalResourceId'])
            event['Status'] = 'SUCCESS'
            return send(event)

        elif event['RequestType'] == 'Update':

            if replace_cert(event):
                event['PhysicalResourceId'] = create_cert(props, i_token)
                add_tags(event['PhysicalResourceId'], props)
                validate(event['PhysicalResourceId'], props)

                if not wait_for_issuance(event['PhysicalResourceId'], context):
                    return reinvoke(event, context)
            else:
                if 'Tags' in event['OldResourceProperties']:
                    acm.remove_tags_from_certificate(CertificateArn=event['PhysicalResourceId'],
                                                     Tags=event['OldResourceProperties']['Tags'])

                add_tags(event['PhysicalResourceId'], props)

            event['Status'] = 'SUCCESS'
            return send(event)
        else:
            raise RuntimeError('Unknown RequestType')

    except Exception as ex:
        l.exception('')
        event['Status'] = 'FAILED'
        event['Reason'] = str(ex)
        return send(event)

Becomes:

def handler(event,context):
	L='OldResourceProperties';K='Tags';J='None';H='SUCCESS';G='RequestType';E='Status';D=context;B='PhysicalResourceId';A=event;l.info(A)
	try:
		F=hashlib.new('md5',(A['RequestId']+A['StackId']).encode()).hexdigest();C=A['ResourceProperties']
		if A[G]=='Create':
			A[B]=J;A[B]=create_cert(C,F);add_tags(A[B],C);validate(A[B],C)
			if wait_for_issuance(A[B],D):A[E]=H;return send(A)
			else:return reinvoke(A,D)
		elif A[G]=='Delete':
			if A[B]!=J:acm.delete_certificate(CertificateArn=A[B])
			A[E]=H;return send(A)
		elif A[G]=='Update':
			if replace_cert(A):
				A[B]=create_cert(C,F);add_tags(A[B],C);validate(A[B],C)
				if not wait_for_issuance(A[B],D):return reinvoke(A,D)
			else:
				if K in A[L]:acm.remove_tags_from_certificate(CertificateArn=A[B],Tags=A[L][K])
				add_tags(A[B],C)
			A[E]=H;return send(A)
		else:raise RuntimeError('Unknown RequestType')
	except Exception as I:l.exception('');A[E]='FAILED';A['Reason']=str(I);return send(A)

Why?

AWS Cloudformation templates may have AWS lambda function source code embedded in them, but only if the function is less than 4KiB. I wrote this package so I could write python normally and still embed the module in a template.

Installation

To install python-minifier use pip:

$ pip install python-minifier

Note that python-minifier depends on the python interpreter for parsing source code, so install using a version of python appropriate for your source.

python-minifier runs with and can minify code written for Python 2.7 and Python 3.3 to 3.10.

Usage

To minify a source file, and write the minified module to stdout:

$ pyminify hello.py

There is also an API. The same example would look like:

import python_minifier

with open('hello.py') as f:
    print(python_minifier.minify(f.read()))

Documentation is available at dflook.github.io/python-minifier/

License

Available under the MIT License. Full text is in the LICENSE file.

Copyright (c) 2020 Daniel Flook

Comments
  • Automatically strip type hints when needed

    Automatically strip type hints when needed

    • Fixed an issue that was preventing me from importing on python3
    • Added type hint removal
    • Automatically applied if AST fails to parse the module
    • Found In src/python_minifier/transforms/remove_hints.py
    • Removal code is generated from selected files from 'strip-hint' package
    • All of the original comments from 'strip-hints' are maintained
    • A script is included to regenerate 'remove_hints.py' from tlatest master as desired (e.g. if python3 grammar changes to include more funkt syntax in type hints, which has happened several times 😂)

    Comments / feedback appreciated! Love your package. ❤

    opened by greyblue9 6
  • Minify recursively

    Minify recursively

    It's probably a hard feature request, but would be great run "pyminify -r hello.py" to minify the file & the libraries its using

    e.g. for projects with a lot of dependencies which want to have a .exe or even a .apk, as kivy projects (they tend to be much bigger than normal apks)

    is it viable?

    opened by ntaraujo 5
  • Allow encoding specification - UnicodeEncodeError: 'charmap' codec can't encode character

    Allow encoding specification - UnicodeEncodeError: 'charmap' codec can't encode character

    I was trying to minify a file that contains lines as below in utf-8 format: print("≡")

    Currently it fails with UnicodeEncodeError: 'charmap' codec can't encode character '\u2030' in position 8: character maps to <undefined>

    A fix would be if the encoding is specified when the file is opened with open('filename', 'w', encoding='utf-8') as f:

    Maybe allow something like? pyminifer --encoding 'utf-8' file.py

    opened by JMSS-Unknown 5
  • Minor Issue - If the code has © symbol it won't run after using minifier...

    Minor Issue - If the code has © symbol it won't run after using minifier...

    When I try to run the converted code it returns the following error: (unicode error) 'utf-8' codec can't decode byte 0xa9 in position 29: invalid start byte

    I had a string that had a copyright symbol, I've remove the symbol and it works.

    Thanks

    opened by TheWicklowWolf 4
  • UnicodeEncodeError

    UnicodeEncodeError

    An issue related to this is already closed. But it seems that it is not yet completely fixed.

    The error occurs on emojis.

    This is the exact command I used:

    pyminify folder --in-place --remove-literal-statements
    

    Versions:

    • python 3.10.7
    • python-minifier 2.6.0
    opened by NadieFiind 4
  • Duplicate parameter names generated with python 2.7

    Duplicate parameter names generated with python 2.7

    Hi, very nice library/application! I'm using it to reduce the size of MyPaint's appimage files (by a fair chunk actually).

    Prelude

    Version used: 2.3.0

    When pre-minifying the libraries I ran into an issue with files that had already been minified before, where pyminify produced invalid parameters. As far as I can tell, this only happens when running with python 2.7, but not 3.5+ (tested with 2.7.5 and 2.7.12).

    Problem

    The same name is assigned to the *args and the **kwds parameters, yielding invalid code.

    Minimal example

    Input:

    class Q:
    	def f(C,a,*A,**B):D='.';super(Q,C).f(a,*A,**B);D or D
    

    when minified, yields the invalid output:

    class Q:
    	def f(D,a,*C,**C):A='.';super(Q,D).f(a,*B,**C);A or A
    

    where *C really should be *B (classic off by one, but due to some py2/3 semantic difference?)

    The minimal example is itself a (valid) minification of:

    class Q:
    
        def f(self, a, *b, **c):
            super(Q, self).f(a, *b, **c)
            '.' or '.'
    
    defect 
    opened by jplloyd 4
  • Replace newlines with semicolons wherever possible? (feature request)

    Replace newlines with semicolons wherever possible? (feature request)

    Hello!

    So I noticed that python-minifier, when for example encounters a builtin function used multiple times, assigns it to a variable at the very beginning: E=print However, when it does so multiple times, they are put each in their own line. On Windows, a new line consists of a carriage return and line break. It is 2 bytes on its own. Using a semicolon would take only 1 byte.

    How would one go about making this change? I would make a pull request myself if I find any time to do it myself

    I opened this issue to discuss, and point out possible drawbacks of this

    (I know that by default it inserts only LINE FEED character, but when porting code across multiple machines this might or might not break?)

    enhancement 
    opened by IamMusavaRibica 3
  • Inconsistency between stdin and file input

    Inconsistency between stdin and file input

    sys.stdin.read() reads a UTF-8 string, but a file is opened in binary mode (and therefore reads bytes). Either bytes should be read from sys.stdin.buffer, or the file should be opened in text mode.

    opened by clbarnes 3
  • `--remove-literal-statements` doesn't work

    `--remove-literal-statements` doesn't work

    Hi!

    First of all, thank you very much for writing this software. It is very useful to decrease the size of Python's standard library when exposing it for web-apps running with Pyodide, a full CPython 3.8 compiled to web-assembly and running in the browser.

    Anyway, it seems I found a bug, because the --remove-literal-statements does not seem to have any effect on the provided sources. These two calls return exactly the same result. Docstrings are everywhere.

    $ pyminify  0.py >a.txt 
    $ pyminify --remove-literal-statements 0.py  >b.txt 
    

    Attached are the results I got. I'm on pyminify 2.4.1 as installed from PyPI, the used Python version for execution is 3.9.1, but I also tested it with 3.8.2 with the same result.

    0.py => this is the __init.py__ of Python 3.8.2's collections standard library package a.txt b.txt

    opened by phorward 3
  • Function names are not changed for some files

    Function names are not changed for some files

    Hi, thanks for this really useful tool.

    I have been testing it with some of my files and I see that for some files, the function names get changed but for some they don't. Unfortunately I can't paste the one that doesn't get the names changed. Do you have any reason in mind as to why that could be happening? If not, I will put more work into trying to create a working example that I can share.

    For this file, however, the functions do get renamed:

    # test_file.py
    global_var = 3
    
    def test_function(prep_text, entites):
        '''
        test comment
        '''
        # single line comment
        test_var_name = {
            'blabla': '1',
            'blabla2': '2'
        }
    
        myset = {'one', 'two', 'three'}
        print(myset)
    
        return test_var_name
    
    
    def test_func_1(var1):
        print('bla')
    
    def test_func_2(var2):
        test_func_1(var1)
    
    def test_func_3(var1):
        test_func_2('blabla')
    
    $ pyminify --remove-literal-statements --rename-globals --no-hoist-literals test_file.py
    C=print
    D=3
    def E(prep_text,entites):A={'blabla':'1','blabla2':'2'};B={'one','two','three'};C(B);return A
    def A(var1):C('bla')
    def B(var2):A(var1)
    def F(var1):B('blabla')
    
    opened by fersarr 3
  • pyminify strips typing from NamedTuple replacing them with 0s?

    pyminify strips typing from NamedTuple replacing them with 0s?

    Example: class MyTuple(NamedTuple):url:0;domain:0;sitekey:0;kind:0;action:0

    Should be: class MyTuple(NamedTuple):url:str;domain:str;sitekey:str;kind:CaptchaKindEnum;action:str

    Is there any way to keep this from happening?

    opened by NoahCardoza 3
  • Make transformation of class annotations configurable to fix issue with transitive inheritance from TypedDict

    Make transformation of class annotations configurable to fix issue with transitive inheritance from TypedDict

    First of all, huge thanks for providing this fantastic library! 🙌

    We've discovered a small issue with type declarations and transitive class inheritance. If we use this sample code:

    from typing import Optional, TypedDict
    class A(TypedDict):
        arg1: str
    class B(A):
        arg2: Optional[int]
    class C(B):
        arg3: Optional[str]
    

    ... it would get minified to:

    from typing import Optional,TypedDict
    class A(TypedDict):arg1:str
    class B(A):arg2:0
    class C(B):arg3:0
    

    ... which is invalid Python code (tested under 3.8, 3.10, but also applies to other versions):

    $ python test.py
      ...
      File "~/.pyenv/versions/3.10.4/lib/python3.10/typing.py", line 176, in _type_check
        raise TypeError(f"{msg} Got {arg!r:.100}.")
    TypeError: TypedDict('Name', {f0: t0, f1: t1, ...}); each t must be a type Got 0.
    

    The issue is that the library is currently not able to detect transitive inheritance dependencies (for good reasons, as this is a hard problem - probably infeasible to solve in the general case, as class hierarchies may be distributed across different source files, and minification is only performed within the scope of a single source file.)

    One solution is to use the remove_annotations=False flag to retain all annotations, but in a larger codebase it can actually be beneficial to remove type declarations, especially from function/method args.

    The code in question is this piece: https://github.com/dflook/python-minifier/blob/9748fabffcc5954a326f1e95697d00615a8937b5/src/python_minifier/transforms/remove_annotations.py#L100-L106 The ideal case would be if we can introduce a config flag to disable the transformation that is happening for class attributes in this block.

    @dflook Would it make sense to distinguish between (1) function annotations and (2) class annotations, and introduce a new flag remove_annotations_class? We could then either introduce a new SuiteTransformer for class annotations, or alternatively leave the current structure and skip executing visit_AnnAssign if remove_annotations_class is False. Happy to work towards a PR, but would like to get your thoughts and guidance first.. 👍 Thanks!

    enhancement 
    opened by whummer 1
  • print

    print

    Hi There,

    I have found an issue. The description says that Python 2.7 is supported, but it gives and error on "print" function

    Missing parentheses in call to 'print'. Did you mean print(boolObjL)?

    Parentheses is not required for print in Python 2.7.

    opened by andrasaa 2
  • Python Minifier doesn't rename variables when eval() is used.

    Python Minifier doesn't rename variables when eval() is used.

    When I try to shorten this code with all options enabled it only removes the spaces.

    equation = input().split("=")[1]
    grid = [["."]*10 for i in range(10)]
    for y in range(10):
        x = int(eval(equation))
        if 0 <= x <= 9:
            grid[y][x] = "o"
    
    print('\n'.join("".join(j for j in i) for i in grid))
    

    Result: Reduction from 217 to 192

    equation=input().split('=')[1]
    grid=[['.']*10 for i in range(10)]
    for y in range(10):
    	x=int(eval(equation))
    	if 0<=x<=9:grid[y][x]='o'
    print('\n'.join((''.join((j for j in i))for i in grid)))
    

    When the eval() is removed from the code it gets shortened properly (203 to 162)

    D=range
    E=input().split('=')[1]
    A=[['.']*10 for A in D(10)]
    for C in D(10):
    	B=int()
    	if 0<=B<=9:A[C][B]='o'
    print('\n'.join((''.join((A for A in B))for B in A)))
    
    enhancement 
    opened by Niikurasu 3
  • pyproject.toml: python-minifier as build-backend

    pyproject.toml: python-minifier as build-backend

    Modern Python projects define their build requirements in pyproject.toml, e.g.,

    [build-system]
    requires = [
      "setuptools>=42",
      "wheel",
    ]
    build-backend = "setuptools.build_meta"
    

    It'd be nice addition of python-minifier could be defined as the builder, e.g.,

    [build-system]
    requires = [
      "setuptools>=42",
      "wheel",
      "python-minifier>=xyz",
    ]
    build-backend = "python_minifier.build_meta"
    

    to spit out minified package code.

    opened by nschloe 0
  • Replace literal-statements by short string in case __doc__ is used when remove_literal_statements is wanted

    Replace literal-statements by short string in case __doc__ is used when remove_literal_statements is wanted

    This improves the remove_literal_statements-feature and replaces doc-strings in case the module uses doc by the string 'doc-string stripped by python-minifier', which is much shorter in most cases.

    Draft for issue #38.

    opened by phorward 0
Releases(2.8.0)
  • 2.8.0(Dec 27, 2022)

    Added

    • New transforms that together work similarly to Python's -O option
      • Remove asserts, which removes assert statements and is disabled by default
      • Remove debug, which removes any if block that tests __debug__ is True and is disabled by default

    Changed

    • When minifiying a directory, files ending with '.pyw' will now be minified.
    Source code(tar.gz)
    Source code(zip)
  • 2.7.0(Oct 27, 2022)

    Added

    • Python 3.11 support, including exception groups syntax

    Changed

    • Improved detection of dataclasses when using the remove annotations transform, which suppresses removal of annotations for those classes

    Fixed

    • Renamed nonlocal names could be incorrect if the name isn't local in the immediate parent function scope. (or it was bound in the immediate parent, but after the definition of the nested scope)
    Source code(tar.gz)
    Source code(zip)
  • 2.6.0(Apr 10, 2022)

    Added

    • A new option to preserve the shebang line from the source file, which is enabled by default
    • More flexible file processing options for the pyminify command:
      • A new --output argument for writing the minified output to a file without having to use shell redirection
      • A new --in-place option which overwrites the specified path with the minified output
      • path arguments may be directories, which minifies all *.py files below that directory
      • Multiple path arguments may be specified, which will all be minified
    • Type information is included in the package to enable type checking of the public functions

    Fixed

    • No longer assumes files read from stdin are utf-8.
    Source code(tar.gz)
    Source code(zip)
  • 2.5.0(Oct 6, 2021)

  • 2.4.2(Jun 28, 2021)

    Fixed

    • Rare Exceptions when encountering empty f-string str parts
    • Missing required parentheses in return statements for iterable unpacking in python <3.8
    • Missing parentheses in some complex dict expansions

    Removed

    • Python 2.6 support
    Source code(tar.gz)
    Source code(zip)
  • 2.4.1(Oct 17, 2020)

  • 2.4.0(Oct 15, 2020)

  • 2.3.2(Oct 11, 2020)

  • 2.3.1(May 4, 2020)

  • 2.3.0(Nov 18, 2019)

    Added

    • Optional source transform:
      • convert positional only arguments to normal arguments, enabled by default

    Fixed

    • Unnecessary spaces after ',' in tuple values
    • Removing annotations for positional-only arguments (Thanks luk3yx!)
    • --no-remove-annotations argument to pyminify had no effect
    Source code(tar.gz)
    Source code(zip)
  • 2.2.1(Nov 3, 2019)

  • 2.1.2(Jun 27, 2019)

  • 2.1.1(Apr 7, 2019)

  • 2.1.0(Jan 24, 2019)

    Added

    • Optional source transforms:
      • remove object base, enabled by default

    Changed

    • Return statements no longer wrap tuples in extraneous parentheses
    • Duplicated literals are only raised to the lowest common function namespace
    Source code(tar.gz)
    Source code(zip)
  • 2.0.0(Jan 13, 2019)

    Added

    • Optional source transformations:
      • Rename locals, enabled by default
      • Rename globals, disabled by default

    Changed

    • Minified code will no longer have leading or trailing whitespace
    • Generated names for hoisted literals will have an initial underscore if rename globals is disabled
    • Suites of simple statements won't create an indented block
    • All transforms are now functional on all supported python versions
    • The module docstring is not removed by the remove literal statements transformation if there is a name bound for it

    Fixed

    • Python 3.7 dataclass field annotations are no longer removed when the remove annotation transformation is enabled.
    Source code(tar.gz)
    Source code(zip)
  • 1.1.0(Jan 13, 2019)

    Added

    • Optional source transformations:
      • Combine import statements
      • Remove annotations
      • Remove pass statements
      • Remove unused literals, including docstrings
      • Move duplicated literals into module level variables
    Source code(tar.gz)
    Source code(zip)
  • 1.0.0(Jan 13, 2019)

Owner
Daniel Flook
Daniel Flook
Wisdom Tree is a concentration app i am working on.

Wisdom Tree Wisdom Tree is a tui concentration app I am working on. Inspired by the wisdom tree in Plants vs. Zombies which gives in-game tips when it

NO ONE 241 Jan 01, 2023
「📖」Tool created to extract metadata from a domain

Metafind is an OSINT tool created with the aim of automating the search for metadata of a particular domain from the search engine known as Google.

9 Dec 28, 2022
The little-endian version of MessagePack

MessagePackEL This is the little-endian version of MessagePack, except the endianness is different, the rest is exactly the same as MessagePack. C lib

dukelec 9 May 13, 2022
Intelligent Systems Project In Python

Intelligent Systems Project In Python

RLLAB 3 May 16, 2022
Tool to audit and fix Python project requirements.

Requirement Auditor Utility to revise and updated python requirement files.

Luis Carlos Berrocal 1 Nov 07, 2021
Collaboration project to creating bank application maded by Anzhelica Sakun and Yuriy Konyukh

Collaboration project to creating bank application maded by Anzhelica Sakun and Yuriy Konyukh

Yuriy 1 Jan 08, 2022
For radiometrically calibrating and PSF deconvolving IRIS data

irispreppy For radiometrically calibrating and PSF deconvolving IRIS data. I dislike how I need to own proprietary software (IDL) just to simply prepa

Aaron W. Peat 4 Nov 01, 2022
With the initiation of the COVID vaccination drive across India for all individuals above the age of 18, I wrote a python script which alerts the user regarding open slots in the vicinity!

cowin_notifier With the initiation of the COVID vaccination drive across India for all individuals above the age of 18, I wrote a python script which

13 Aug 01, 2021
Automatização completa do site https://blaze.com

PyBlaze Pyblaze possibilita o acesso a api do site blaze utilizando python, retornando os últimos resultados de crashs e doubles. Agora também é possí

Cleiton Leonel 24 Dec 30, 2022
Tutorial on Tempo, Beat and Downbeat estimation

Tempo, Beat and Downbeat Estimation By Matthew E. P. Davies, Sebastian Böck and Magdalena Fuentes Resources and Jupyter Book for the ISMIR 2021 tutori

49 Nov 06, 2022
Code repo for the book "Feature Engineering for Machine Learning," by Alice Zheng and Amanda Casari, O'Reilly 2018

feature-engineering-book This repo accompanies "Feature Engineering for Machine Learning," by Alice Zheng and Amanda Casari. O'Reilly, 2018. The repo

Alice Zheng 1.3k Dec 30, 2022
A simple and convenient build-and-run system for C and C++.

smake Smake is a simple and convenient build-and-run system for C and C++ projects. Why make another build system? CMake and GNU Make are great build

Venkataram Edavamadathil Sivaram 18 Nov 13, 2022
HiSim - House Infrastructure Simulator

HiSim is a Python package for simulation and analysis of household scenarios using modern components as alternative to fossil fuel based ones.

FZJ-IEK3 17 Dec 17, 2022
Simple Python-based web application to allow UGM students to fill their QR presence list without having another device in hand.

Praesentia Praesentia is a simple Python-based web application to allow UGM students to fill their QR presence list without having another device in h

loncat 20 Sep 29, 2022
Short, introductory guide for the Python programming language

100 Page Python Intro This book is a short, introductory guide for the Python programming language.

Sundeep Agarwal 185 Dec 26, 2022
Python pyside2 kütüphanesi ile oluşturduğum drone için yer kontrol istasyonu yazılımı.

Ground Control Station (Yer Kontrol İstasyonu) Teknofest yarışmasında yerlilik kısmında Yer Kontrol İstasyonu yazılımı seçeneği bulunuyordu. Bu yüzden

Emirhan Bülbül 4 May 14, 2022
Get the stats of a (or more) Hypixel player(s)

Hypixel_Stats Get the statistics of a (or more) Hypixel player(s) Who needs this? Everyone who plays a lot of Minecraft and often plays on mc.hypixel.

Finnomator 1 Feb 12, 2022
Wordless - the #1 app for helping you cheat at Wordle, which is sure to make you popular at parties

Wordless Wordless is the #1 app for helping you cheat at Wordle, which is sure t

James Kirk 7 Feb 04, 2022
:art: Diagram as Code for prototyping cloud system architectures

Diagrams Diagram as Code. Diagrams lets you draw the cloud system architecture in Python code. It was born for prototyping a new system architecture d

MinJae Kwon 27.5k Jan 04, 2023
Cute study buddy that helps you study with the Pomodoro technique!

study-buddy Cute study buddy that helps you study with the Pomodoro (or Animedoro) technique! Kirby The Kirby folder has a Kirby, pink-themed Pomodoro

Ethan Emmanuel 1 Jan 19, 2022