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
A service to display a quick summary of a project on GitHub.

A service to display a quick summary of a project on GitHub. Usage 📖 Paste the code below with details filled in as specified below into your Readme.

Rohit V 8 Dec 06, 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.

195 Dec 13, 2022
Batch obfuscator based on the obfuscation method used by the trick bot launcher

Batch obfuscator based on the obfuscation method used by the trick bot launcher

SlizBinksman 2 Mar 19, 2022
RFDesign - Protein hallucination and inpainting with RoseTTAFold

RFDesign: Protein hallucination and inpainting with RoseTTAFold Jue Wang (juewan

139 Jan 06, 2023
Imports an object based on a string import_string('package.module:function_name')() - Based on werkzeug.utils

DEPRECATED don't use it. Please do: import importlib foopath = 'src.apis.foo.Foo' module_name = '.'.join(foopath.split('.')[:-1]) # to get src.apis.f

Bruno Rocha Archived Projects 11 Nov 12, 2022
CRC Reverse Engineering Tool in Python

CRC Beagle CRC Beagle is a tool for reverse engineering CRCs. It is designed for commnication protocols where you often have several messages of the s

Colin O'Flynn 51 Jan 05, 2023
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
LPCV Winner Solution of Spring Team

LPCV Winner Solution of Spring Team

22 Jul 20, 2022
PyLaboratory 0 Feb 07, 2022
This tool allows you to do goole dorking much easier

This tool allows you to do goole dorking much easier

Steven 8 Mar 06, 2022
Tutor plugin for integration of Open edX with a Richie course catalog

Richie plugin for Tutor This is a plugin to integrate Richie, the learning portal CMS, with Open edX. The integration takes the form of a Tutor plugin

Overhang.IO 2 Sep 08, 2022
Discord's own Dumbass made for shits n' Gigs!

FWB3 Discord's own Dumbass made for shits n' Gigs! Please note: This bot is made to be stupid and funny, If you want to get into bot development you'r

1 Dec 06, 2021
Recreate the joys of Office Assistant from the comfort of the Python interpreter

Recreate the joys of Office Assistant from the comfort of the Python interpreter.

Louis Sven Goulet 3 May 21, 2022
A domonic-like wrapper around selectolax

A domonic-like wrapper around selectolax

byteface 3 Jun 23, 2022
Collection of script & resources for Foundry's Nuke software.

Author: Liam Collod. Collections of scripting stuff I wrote for Foundry's Nuke software. Utilisation You can have a look at the README.md file in each

Liam Collod 1 May 14, 2022
Problem 5: Fermat near-misses

Problem 5: Fermat near-misses fermatnearmiss This is a script that computes fermat nearm misses when the -f option is set and requires users to input

CHRIS BYRON (Int0x80) 1 Jan 08, 2022
This Program Automates The Procces Of Adding Camos On Guns And Saving Them On Modern Warfare Guns

This Program Automates The Procces Of Adding Camos On Guns And Saving Them On Modern Warfare Guns

Flex Tools 6 May 26, 2022
Flames Calculater App used to calculate flames status between two names created using python's Flask web framework.

Flames Finder Web App Flames Calculater App used to calculate flames status between two names created using python's Flask web framework. First, App g

Siva Prakash 4 Jan 02, 2022
Nicotine+: A graphical client for the SoulSeek peer-to-peer system

Nicotine+ Nicotine+ is a graphical client for the Soulseek peer-to-peer file sharing network. Nicotine+ aims to be a pleasant, Free and Open Source (F

940 Jan 03, 2023
Algo próximo do ARP

ArpPY Algo parecido com o ARP-Scan. Dependencias O script necessita no mínimo ter o Python versão 3.x instalado e ter o sockets instalado. Executando

Feh's 3 Jan 18, 2022