Last active
October 7, 2022 13:36
-
-
Save xaedes/27dee467f0ebf40fa2e38b0760f42299 to your computer and use it in GitHub Desktop.
a python script which accepts command line arguments for package name to create folders and files for empty pip installable python package with that name.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#-*- mode: python -*- | |
# -*- coding: utf-8 -*- | |
# | |
# made 85% by copilot. | |
# copilot-mk-pip-package.py | |
# --- | |
# a python script which accepts command line arguments for package name to create folders and files for empty pip installable python package with that name. | |
# | |
# 1. creates the directory structure for a python package | |
# 2. creates the setup.py file | |
# 3. creates the README.md file | |
# 4. creates the __init__.py file | |
import os | |
import re | |
import sys | |
import argparse | |
# define required arguments. show help if arguments are not provided. | |
parser = argparse.ArgumentParser(description='Create a new pip installable python package') | |
parser.add_argument('package_name', help='Name of the package') | |
parser.add_argument('--version', help='Version of the package', default='0.0.1') | |
parser.add_argument('--author_name', help='Name of the author', default='Anonymous') | |
parser.add_argument('--author_email', help='Email of the author', default='') | |
parser.add_argument('--tests_folder', help='Name of the tests folder', default='tests') | |
parser.add_argument('--requirements_file', help='Name of the requirements file', default='requirements.txt') | |
parser.add_argument('--license', help='License of the package', default='MIT') | |
args = parser.parse_args() | |
# sanitize package name by replacing spaces and dashes with underscores and converting to lowercase. | |
args.package_name = args.package_name.replace(' ', '_').replace('-', '_').lower() | |
# function to remove indentations from the second line and following lines as indicated by the indentation of the second line, but keeps remaining indentation intact. | |
def remove_indent(text): | |
lines = text.splitlines() | |
if len(lines) < 2: | |
return text | |
indent = len(lines[1]) - len(lines[1].lstrip()) | |
return '\n'.join([lines[0]] + [line[indent:] for line in lines[1:]]) | |
# function to write into file, accepting multiline strings that have additional indentation because they are indented as part of the code. | |
# using remove_indent we can use multiline strings correctly indented in code, which will then be written to file, but | |
# the suplerflous indentation is removed by remove_indent. | |
def write_dedented(file, text): | |
file.write(remove_indent(text)) | |
# print the arguments provided in a pretty format so that the user can verify that the arguments are correct and be hyped about the soon to be new package. | |
# use python ascii art generator library "art" to generate the ascii art of the new package and print it. | |
# add a fancy line to separate the arguments from the rest of the output. | |
import art | |
print(art.text2art(args.package_name)) | |
print(remove_indent(''' | |
Arguments provided: | |
''')) | |
# print args with values aligned by padding spaces | |
print('package_name'.ljust(20), args.package_name) | |
print('version'.ljust(20), args.version) | |
print('author_name'.ljust(20), args.author_name) | |
print('author_email'.ljust(20), args.author_email) | |
print('tests_folder'.ljust(20), args.tests_folder) | |
print('requirements_file'.ljust(20), args.requirements_file) | |
print('license'.ljust(20), args.license) | |
print('\n' + '-'*80 + '\n') | |
package_name = args.package_name | |
print("Creating package {}".format(package_name)) | |
os.makedirs(package_name, exist_ok=True) | |
os.makedirs(os.path.join(package_name, package_name), exist_ok=True) | |
os.makedirs(os.path.join(package_name, args.tests_folder), exist_ok=True) | |
with open(os.path.join(package_name, "README.md"), "w") as f: | |
write_dedented(f, "# {}".format(package_name)) | |
with open(os.path.join(package_name, "setup.py"), "w") as f: | |
# write the setup.py file using the provided arguments. | |
# find_package is used to find the packages from setup.py, but tests are excluded. | |
# find scripts from scripts folder using glob and specify them in setup call. | |
write_dedented(f, """ | |
from setuptools import setup, find_packages | |
import glob, os | |
setup( | |
name='{package_name}', | |
version='{version}', | |
author='{author_name}', | |
author_email='{author_email}', | |
license='{license}', | |
packages=find_packages(exclude=['{tests_folder}']), | |
install_requires=[], | |
entry_points={{'console_scripts': ['{package_name}={package_name}.main:main']}}, | |
) | |
""".format( | |
package_name=package_name, version=args.version, author_name=args.author_name, | |
author_email=args.author_email, tests_folder=args.tests_folder, license=args.license | |
) | |
) | |
# generate package __init__.py file defining version specified in args.version | |
with open(os.path.join(package_name, package_name, "__init__.py"), "w") as f: | |
write_dedented(f, '''__version__ = '{}' | |
'''.format(args.version)) | |
# generate package main.py file with a hello world function | |
with open(os.path.join(package_name, package_name, "main.py"), "w") as f: | |
write_dedented(f, ''' | |
def main(): | |
print("Hello World!") | |
''') | |
# generate tests __init__.py file | |
with open(os.path.join(package_name, "tests", "__init__.py"), "w") as f: | |
write_dedented(f, "") | |
# generate example tests file using pytest, testing the addition of two numbers | |
with open(os.path.join(package_name, "tests", "test_add.py"), "w") as f: | |
write_dedented(f, ''' | |
def test_add(): | |
assert(1+2 == 3) | |
''') | |
# generate requirements.txt file | |
with open(os.path.join(package_name, args.requirements_file), "w") as f: | |
write_dedented(f, "") | |
# generate .gitignore file | |
with open(os.path.join(package_name, ".gitignore"), "w") as f: | |
write_dedented(f, """ | |
*.egg* | |
*.pyc* | |
__pycache__ | |
""") | |
# generate License file. | |
# downloading the license json as specified in args.license from github and parse it. | |
# write the license text to the license file. | |
# if license is not found or not specified in args.license, generate a "all rights reserved" license file instead. | |
all_rights_reserved = False | |
try: | |
import requests | |
import json | |
resp = requests.get('https://api.github.com/licenses/{}'.format(args.license.lower())) | |
if resp.status_code == 200: | |
license = json.loads(resp.text) | |
license_text = license['body'] | |
# replace the '[year]' with current year and replace the '[fullname]' with args.author_name. | |
import datetime | |
license_text = license_text.replace('[year]', str(datetime.datetime.now().year)) | |
license_text = license_text.replace('[fullname]', args.author_name) | |
with open(os.path.join(package_name, "LICENSE"), "w") as f: | |
write_dedented(f, license_text) | |
except: | |
all_rights_reserved = True | |
with open(os.path.join(package_name, "LICENSE"), "w") as f: | |
write_dedented(f, ''' | |
All rights reserved | |
''') | |
# print out the name of the license, or "all rights reserved" if it applies. | |
print("License: {}".format(args.license if not all_rights_reserved else "all rights reserved")) | |
print("Package {} created".format(package_name)) | |
# inform user on console how to install package with pip in dev mode (-e), | |
# how to run the tests, how to run the package main script and how to import | |
# the package from python. | |
print(remove_indent(""" | |
To install the package in dev mode, run: | |
pip install -e {package_name} | |
To run the tests, run: | |
pytest {package_name}/tests | |
To run the package main script, run: | |
{package_name} | |
To import the package from python, run: | |
import {package_name} | |
""".format(package_name=package_name))) | |
# Now we can use this script to create a package with the name we want. | |
# | |
# $ python .\copilot-mk-pip-package.py banana_pie | |
# _ _ | |
# | |__ __ _ _ __ __ _ _ __ __ _ _ __ (_) ___ | |
# | '_ \ / _` || '_ \ / _` || '_ \ / _` | | '_ \ | | / _ \ | |
# | |_) || (_| || | | || (_| || | | || (_| | | |_) || || __/ | |
# |_.__/ \__,_||_| |_| \__,_||_| |_| \__,_| _____ | .__/ |_| \___| | |
# |_____||_| | |
# | |
# Arguments provided: | |
# | |
# package_name: banana_pie | |
# version: 0.0.1 | |
# author_name: Anonymous | |
# author_email: scripts | |
# tests_folder: tests | |
# scripts_folder: scripts | |
# requirements_file: requirements.txt | |
# license: MIT | |
# | |
# -------------------------------------------------------------------------------- | |
# | |
# Creating package banana_pie | |
# License: MIT | |
# Package banana_pie created | |
# | |
# $ tree banana_pie/ | |
# banana_pie/ | |
# ├── banana_pie | |
# │ ├── __init__.py | |
# │ └── main.py | |
# ├── README.md | |
# ├── LICENSE | |
# ├── setup.py | |
# ├── tests | |
# │ ├── __init__.py | |
# │ └── test_add.py | |
# | |
# | |
# And we have a package with the name we want. | |
# | |
# $ pip install -e banana_pie/ | |
# Obtaining file:///home/rohit/banana_pie | |
# Installing collected packages: banana_pie | |
# Running setup.py develop for banana_pie | |
# Successfully installed banana_pie-0.0.1 | |
# | |
# Execute the tests using pytests by running the following command: | |
# $ pytest banana_pie/tests/ | |
# | |
# $ python | |
# Python 3.7.4 (default, Aug 13 2019, 20:35:49) | |
# [GCC 9.2.0] on linux | |
# Type "help", "copyright", "credits" or "license" for more information. | |
# >>> import banana_pie | |
# >>> banana_pie.__version__ | |
# '0.1.0' | |
# >>> | |
# | |
# The package is installed in editable mode, so any changes we make to the package will be reflected immediately. | |
# | |
# $ echo "print('hello world')" >> banana_pie/banana_pie/__init__.py | |
# | |
# $ python | |
# Python 3.7.4 (default, Aug 13 2019, 20:35:49) | |
# [GCC 9.2.0] on linux | |
# Type "help", "copyright", "credits" or "license" for more information. | |
# >>> import banana_pie | |
# hello world | |
# >>> | |
# | |
# If we want to publish this package to pypi, we can use the following command: | |
# $ cd banana_pie/ | |
# $ python setup.py sdist bdist_wheel | |
# | |
# This will generate a dist folder with the package and wheel files. | |
# | |
# $ tree dist/ | |
# dist/ | |
# ├── banana_pie-0.0.1-py3-none-any.whl | |
# └── banana_pie-0.0.1.tar.gz | |
# | |
# We can upload this to pypi using the twine package. | |
# | |
# $ twine upload dist/* | |
# | |
# $ pip install banana_pie | |
# Collecting banana_pie | |
# Downloading https://files.pythonhosted.org/packages/64/6f/6d0f8f8e5f7f0a5a9a7c9c8f3c7a5d0f3d2e3e3a5c5b5f5b5f7d5d5a5e7c/banana_pie-0.0.1-py3-none-any.whl | |
# Installing collected packages: banana-pie | |
# Successfully installed banana-pie-0.0.1 | |
# |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment