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
Advanced Variable Manager {AVM} [0.8.0]

Advanced Variable Manager {AVM} [0.8.0] By Grosse pastèque#6705 WARNING : This modules need some typing modifications ! If you try to run it without t

Big watermelon 1 Dec 11, 2021
An interactive course to git

OperatorEquals' Sandbox Git Course! Preface This Git course is an ongoing project containing use cases that I've met (and still meet) while working in

John Torakis 62 Sep 19, 2022
Convert .1pux to .csv

1PasswordConverter Convert .1pux to .csv 1Password uses this new export format .1pux, I assume stands for 1 Password User eXport. As of right now, 1Pa

Shayne Hartford 7 Dec 16, 2022
Python library to decode the EU Covid-19 vaccine certificate

DCC Utils Python library to decode the EU Covid-19 vaccine certificate, as specified by the EU. Setup pip install dcc-utils Make sure zbar is installe

Developers Italia 13 Mar 11, 2022
A very terrible python-based programming language that uses folders instead of text files

PYFolders by Lewis L. Foster PYFolders is a very terrible python-based programming language that uses folders instead of regular text files. In this r

Lewis L. Foster 5 Jan 08, 2022
Built as part of an assignment for S5 OOSE Subject CSE

Installation Steps: Download and install Python from here based on your operating system. I have used Python v3.8.10 for this. Clone the repository gi

Abhinav Rajesh 2 Sep 09, 2022
It is a personal assistant chatbot, capable to perform many tasks same as Google Assistant plus more extra features...

PersonalAssistant It is an Personal Assistant, capable to perform many tasks with some unique features, that you haven'e seen yet.... Features / Tasks

Roshan Kumar 95 Dec 21, 2022
A novel dual model approach for categorization of unbalanced skin lesion image classes (Presented technical paper 📃)

A novel dual model approach for categorization of unbalanced skin lesion image classes (Presented technical paper 📃)

1 Jan 19, 2022
March-madness - March Madness results 1985-2021

march-madness Results for all 2,268 NCAA Division I Men's Basketball Tournament games since the modern format was introduced in 1985. Includes years,

Darik Harter 2 Feb 26, 2022
A python script to simplify recompiling, signing and installing reverse engineered android apps.

urszi.py A python script to simplify the Uninstall Recompile Sign Zipalign Install cycle when reverse engineering Android applications. It checks if d

Ahmed Harmouche 4 Jun 24, 2022
A general-purpose wallet generator, for supported coins only

2gen A general-purpose generator for keys. Designed for all cryptocurrencies supporting the Bitcoin format of keys and addresses. Functions To enable

Vlad Usatii 1 Jan 12, 2022
[arXiv 2020] Video Representation Learning with Visual Tempo Consistency

Video Representation Learning with Visual Tempo Consistency [Paper] [Project Page] News Full codebae is coming soon Pretained Models For now, we provi

DeciForce: Crossroads of Machine Perception and Autonomy 24 Nov 23, 2022
Simple macOS StatusBar app to remind you to unplug your laptop when sufficiently charged

ChargeMon Simple macOS StatusBar app to monitor battery charge status and remind you to unplug your Mac when the battery is sufficiently charged Overv

Rhet Turnbull 5 Jan 25, 2022
Small tool to use hero .json files created with Optolith for The Dark Eye/ Das Schwarze Auge 5 to perform talent probes.

DSA5-ProbeMaker A little tool for The Dark Eye 5th Edition (Das Schwarze Auge 5) to load .json from Optolith character generation and easily perform t

2 Jan 06, 2022
A simple code for processing images to local binary pattern.

This figure is gotten from this link https://link.springer.com/chapter/10.1007/978-3-030-01449-0_24 LBP-Local-Binary-Pattern A simple code for process

Happy N. Monday 3 Feb 15, 2022
An open source server for Super Mario Bros. 35

SMB35 A custom server for Super Mario Bros. 35 This server is highly experimental. Do not expect it to work without flaws.

Yannik Marchand 162 Dec 07, 2022
VCM EE1.2 P-layer feature map anchor generation 137th MPEG-VCM

VCM EE1.2 P-layer feature map anchor generation 137th MPEG-VCM

IPSL 6 Oct 18, 2022
Clock in automatically in SCU.

auto_clock_in Clock in automatically in SCU. Features send logs to Telegram bot How to use? pip install -r requirements.txt () edit user_list, token_A

2 Dec 13, 2021
Shared utility scripts for AI for Earth projects and team members

Overview Shared utilities developed by the Microsoft AI for Earth team The general convention in this repo is that users who want to consume these uti

Microsoft 38 Dec 30, 2022
Standalone PyQGIS application for executing custom scripts without a QGIS GUI.

PyQGIS Standalone Script Executer Standalone PyQGIS application that is able to run a custom script, in this case Proximity.py without the need of a G

6 Sep 23, 2022