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
Generate Gaussian 09 input files for the rotamers of an input compound.

Rotapy Purpose Generate Gaussian 09 input files for the rotamers of an input compound. Distance to the axis of rotation remains constant throughout th

1 Jul 16, 2021
Data wrangling & common calculations for results from qMem measurement software

qMem Datawrangler This script processes output of qMem measurement software into an Origin ® compatible *.csv files and matplotlib graphs to quickly v

Julian 1 Nov 30, 2021
A synchronous, single-threaded interface for starting processes on Linux

A synchronous, single-threaded interface for starting processes on Linux

Spencer Baugh 27 Jan 28, 2022
Exploring basic lambda calculus in Python

Lambda Exploring basic lambda calculus in Python. In this repo I have used the lambda function built into python to get a more intiutive feel of lambd

Bhardwaj Bhaskar 2 Nov 12, 2021
Modern API wrapper for Genshin Impact built on asyncio and pydantic.

genshin.py Modern API wrapper for Genshin Impact built on asyncio and pydantic.

sadru 212 Jan 06, 2023
A python program with an Objective-C GUI for building and booting OpenCore on both legacy and modern Macs

A python program with an Objective-C GUI for building and booting OpenCore on both legacy and modern Macs, see our in-depth Guide for more information.

dortania 4.7k Jan 02, 2023
Better firefox bookmarks script for rofi

rofi-bookmarks Small python script to open firefox bookmarks with rofi. Features Icons! Only show bookmarks in a specified bookmark folder Show entire

32 Nov 10, 2022
Apache Airflow - A platform to programmatically author, schedule, and monitor workflows

Apache Airflow Apache Airflow (or simply Airflow) is a platform to programmatically author, schedule, and monitor workflows. When workflows are define

The Apache Software Foundation 28.6k Dec 28, 2022
Pampy: The Pattern Matching for Python you always dreamed of.

Pampy: Pattern Matching for Python Pampy is pretty small (150 lines), reasonably fast, and often makes your code more readable and hence easier to rea

Claudio Santini 3.5k Dec 30, 2022
A tool to flash .ofp files in bootloader mode without needing MSM Tool, an alternative to official realme tool

Oppo/Realme Flash .OFP File on Bootloader A tool to flash .ofp files in bootloader mode without needing MSM Tool, an alternative to official realme to

Italo Almeida 70 Jan 02, 2023
HSPyLib is a Python library that will elevate your experience to another level.

HomeSetup Python Library - HSPyLib Your mature python application HSPyLib is a Python library that will elevate your experience to another level. It r

Hugo Saporetti Junior 4 Dec 14, 2022
A frontend to ease the use of pulseaudio's routing capabilities, mimicking voicemeeter's workflow

Pulsemeeter A frontend to ease the use of pulseaudio's routing capabilities, mimicking voicemeeter's workflow Features Create virtual inputs and outpu

Gabriel Carneiro 164 Jan 04, 2023
HPomb Is Socail Engineering Tool , Used For Bombing , Spoofing and Anonymity Available For Linux And Android(Termux)

HPomb v2020.02 Coming Soon Created By Secanonm HPomb Is Socail Engineering Tool , Used For Bombing , Spoofing and Anonymity Available For Linux And An

Secanonm 10 Jul 25, 2022
A collection of repositories used to realise various end-to-end high-level synthesis (HLS) flows centering around the CIRCT project.

circt-hls What is this?: A collection of repositories used to realise various end-to-end high-level synthesis (HLS) flows centering around the CIRCT p

29 Dec 14, 2022
OpenTracing API for Python

OpenTracing API for Python This library is a Python platform API for OpenTracing. Required Reading In order to understand the Python platform API, one

OpenTracing API 767 Dec 16, 2022
A Python utility belt containing simple tools, a stdlib like feel, and extra batteries. Hashing, Caching, Timing, Progress, and more made easy!

Ubelt is a small library of robust, tested, documented, and simple functions that extend the Python standard library. It has a flat API that all behav

Jon Crall 638 Dec 13, 2022
Is a util for xferring skinning from one mesh to another

maya_pythonplugins skinTo: Is a util for xferring skinning from one mesh to another args: :param maxInfluences: is the number of max influences on the

James Dunlop 2 Jan 24, 2022
Cross-platform .NET Core pre-commit hooks

dotnet-core-pre-commit Cross-platform .NET Core pre-commit hooks How to use Add this to your .pre-commit-config.yaml - repo: https://github.com/juan

Juan Odicio 5 Jul 20, 2021
Adam with minor modifications which give significant improvement

BAdam Modification of Adam [1] optimizer with increased stability and better performance. Tricks used: Decoupled weight decay as in AdamW [2]. Such de

19 May 11, 2022
Improved version calculator, now using while True and etc

CalcuPython_2.0 Olá! Calculadora versão melhorada, agora usando while True e etc... melhorei o design e os carai tudo (rode no terminal, pra melhor ex

Scott 2 Jan 27, 2022