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
Table (Finnish Taulukko) glued together to transform into hands-free living.

taulukko Table (Finnish Taulukko) glued together to transform into hands-free living. Installation Preferred way to install is as usual (for testing o

Stefan Hagen 2 Dec 14, 2022
Transform a Google Drive server into a VFX pipeline ready server

Google Drive VFX Server VFX Pipeline About The Project Quick tutorial to setup a Google Drive Server for multiple machines access, and VFX Pipeline on

Valentin Beaumont 17 Jun 27, 2022
The most widely used Python to C compiler

Welcome to Cython! Cython is a language that makes writing C extensions for Python as easy as Python itself. Cython is based on Pyrex, but supports mo

null 7.6k Jan 3, 2023
Python most simple|stupid programming language (MSPL)

Most Simple|Stupid Programming language. (MSPL) Stack - Based programming language "written in Python" Features: Interpretate code (Run). Generate gra

Kirill Zhosul 14 Nov 3, 2022
CBLang is a programming language aiming to fix most of my problems with Python

CBLang A bad programming language made in Python. CBLang is a programming language aiming to fix most of my problems with Python (this means that you

Chadderbox 43 Dec 22, 2022
This program goes thru reddit, finds the most mentioned tickers and uses Vader SentimentIntensityAnalyzer to calculate the ticker compound value.

This program goes thru reddit, finds the most mentioned tickers and uses Vader SentimentIntensityAnalyzer to calculate the ticker compound value.

null 195 Dec 13, 2022
A step-by-step tutorial for how to work with some of the most basic features of Nav2 using a Jupyter Notebook in a warehouse environment to create a basic application.

This project has a step-by-step tutorial for how to work with some of the most basic features of Nav2 using a Jupyter Notebook in a warehouse environment to create a basic application.

Steve Macenski 49 Dec 22, 2022
Wrappers around the most common maya.cmds and maya.api use cases

Maya FunctionSet (maya_fn) A package that decompose core maya.cmds and maya.api features to a set of simple functions. Tests The recommended approach

Ryan Porter 9 Mar 12, 2022
The most hackable keyboard in all the land

MiRage Modular Keyboard © 2021 Zack Freedman of Voidstar Lab Licensed Creative Commons 4.0 Attribution Noncommercial Share-Alike The MiRage is a 60% o

Zack Freedman 558 Dec 30, 2022
The goal of this program was to find the most common color in my living room.

The goal of this program was to find the most common color in my living room. I found a dataset online with colors names and their corr

null 1 Nov 9, 2021
Supply Chain will be a SAAS platfom to provide e-logistic facilites with most optimal

Shipp It Welcome To Supply Chain App [ Shipp It ] In "Shipp It" we are creating a full solution[web+app] for a entire supply chain from receiving orde

SAIKAT_CLAW 25 Dec 26, 2022
A chain of stores wants a 3-month demand forecast for its 10 different stores and 50 different products.

Demand Forecasting Objective A chain store wants a machine learning project for a 3-month demand forecast for 10 different stores and 50 different pro

null 2 Jan 6, 2022
Simple rofi script to choose player for playerctl to execute its command

rofi-playerctl-switcher simple rofi script to choose player for playerctl to execute its command Usage copy playerSwitch.py and playerctl.sh to ~/.con

null 2 Jan 3, 2022
Given an array of integers, calculate the ratios of its elements that are positive, negative, and zero.

Given an array of integers, calculate the ratios of its elements that are positive, negative, and zero. Print the decimal value of each fraction on a new line with places after the decimal.

Shruti Dhave 2 Nov 29, 2021
Automates the fixing of problems reported by yamllint by parsing its output

yamlfixer yamlfixer automates the fixing of problems reported by yamllint by parsing its output. Usage This software automatically fixes some errors a

OPT Nouvelle Caledonie 26 Dec 26, 2022
A timer for bird lovers, plays a random birdcall while displaying its image and info.

Birdcall Timer A timer for bird lovers. Siriema hatchling by Junior Peres Junior Background My partner needed a customizable timer for sitting and sta

Marcelo Sanches 1 Jul 8, 2022
CBO uses its Capital Tax model (CBO-CapTax) to estimate the effects of federal taxes on capital income from new investment

CBO’s CapTax Model CBO uses its Capital Tax model (CBO-CapTax) to estimate the effects of federal taxes on capital income from new investment. Specifi

Congressional Budget Office 7 Dec 16, 2022
Its a simple and fun to use application. You can make your own quizes and send the lik of the quiz to your friends.

Quiz Application Its a simple and fun to use application. You can make your own quizes and send the lik of the quiz to your friends. When they would a

Atharva Parkhe 1 Feb 23, 2022
chiarose(XCR) based on chia(XCH) source code fork, open source public chain

chia-rosechain 一个无耻的小活动 | A shameless little event 如果您喜欢这个项目,请点击star 将赠送您520朵玫瑰,可以去 facebook 留下您的(xcr)地址,和github用户名。 If you like this project, please

ddou123 376 Dec 14, 2022