Coder Social home page Coder Social logo

pfython / easypypi Goto Github PK

View Code? Open in Web Editor NEW
14.0 4.0 1.0 1.75 MB

easyPyPI (Pronounced "Easy Pie-Pea-Eye") is a quick, simple, one-size-fits-all solution for sharing your Python creations on the Python Package Index (PyPI) so others can just pip install your_script with no fuss.

License: MIT License

Python 100.00%

easypypi's Introduction

easyPyPI

easyPyPI (Pronounced "Easy Pie-Pea-Eye") is a quick, simple, one-size-fits-all solution for sharing your Python creations on the Python Package Index (PyPI) so others can just pip install your_script with no fuss.

easyPyPI is mainly intended for Pythonistas who've been put off publishing to PyPI before now or tried it but, like the author (pictured below) thought:

"There must be an easier way to do this!"

Well now there is! With easyPyPI you don't have to spend hours...

  • Reading tutorials about distutils only to realise setuptools is what you need.
  • Reading yet more tutorials just to work out the essential steps (below).
  • Manually creating a folder structure and moving your script(s) there.
  • Manually creating a skeleton README.md
  • Manually creating a skeleton __init__.py
  • Manually creating a skeleton test_yourscript.py
  • Manually creating and updating a LICENSE
  • Manually creating a setup.py script and wondering what on earth to put in it
  • Remembering to update your Version number each time you publish
  • Running setup.py in just the right way to create your distribution files
  • Installing and running twine in just the right way to publish your package to Test PyPI then PyPI
  • Creating a new Repository on Github and uploading the folders and files you just created.
  • Setting environment variables or creating a .pypirc file for twine to use
  • Getting your Test PyPI and PyPI credentials mixed up

Enjoy!

1. QUICKSTART

c:\> pip install easypypi

>>> from easypypi import Package
>>> package = Package()

# or:
>>> package = Package("your_package_name")

Then just follow the prompts to provide the information required to describe your package on PyPI. No knowledge of setuptools, twine, or how to write a setup.py script required.

Once you've gone through the creation process fully (or even partially), your responses are stored in a JSON config file located in the 'usual' settings folder for your Operating System. When you start again easyPyPI will helpfully remember your previous answers.

When you've added all the information you want to include with your package, click the Upversion button to update your PEP440 compliant version number as required, then click the Generate button to create a basic folder structure and populate it with all the standard files you'll need such as a README and LICENSE.

The next time you run easyPyPI with an existing package name and folder location, it will automatically import the contents of the latest setup.py file it finds (in preference to config.json), so if you want you can make updates directly to setup.py but be careful to keep the same basic format so easyPyPI has a chance of finding what it needs!

Finally, when you're ready you can Publish your package folders and files to PyPI and/or Test PyPI, and even automatically create an initial Repository on Github. There are buttons for quickly Registering for a PyPI, Test PyPI, and/or Github account if you don't already have that sorted, and also for installing Git if it's your first time using that too.

2. UPDATING YOUR PACKAGE

For more precise control you can close the GUI after creating your package object, and manually get and set all of the data encapsulated in it. Thanks to the magic of cleverdict you can do this either using object.attribute or dictionary['key'] notation, whichever you prefer:

>>> package.name
'as_easy_as_pie'

>>> package['email'] = "[email protected]"

>>> package['license_dict'].name
'MIT License'

>>> package.version = "2.0.1a1"

3. OTHER FEATURES

To find where easyPyPI and its default templates were installed:

>>> package.easypypi_dirpath

To find the location of your JSON config file to manually inspect, edit, or os.remove() it:

>>> package.config_path
# This should be under the default Settings folder for your Operating System.

To locate your package's setup.py:

>>> package.setup_filepath

If you have extra files which you want to copy into the new folder structure, including the main script file you might have already created before deciding to make it into a package:

>>> package.copy_other_files()

To see what else you can play with using your Package object:

>>> package.keys()
# You can then get/set values using object.attribute or dictionary['key'] notation

esyPyPI uses keyring to store credentials. To manage these credentials manually:

>>> account = "Github"  # or "PyPI" or "Test_PyPI"
>>> package.Github_username = "testuser"

>>> package.get_username(account) == package.Github_username == "testuser"
True

>>> package.set_password(account, "testpw")  # Prompts for pw if none given
True

>>> package.Github_password
'testpw'

>>> package.delete_credentials(account)

4. CONTRIBUTING

easyPyPI was developed in the author's spare time and is hopefully at a stage where it works well and reliably for the original use case. If you'd like to get get involved please do log any Issues (bugs or feature requests) on Github, or if you feel motiviated to work on any of the existing Issues that would be brilliant!

If you're tinkering with the code and have just Cloned it, you'll probably need to be in the parent directory/parent under which you copied easyPyPI and use the following import incantation:

>>> from easypypi.easypypi import *

If you excited enough by the potential of this package to contribute some code, please follow this simple process:

  • Fork this repository. Also STAR this repository for bonus karma!
  • Create a new Branch for the issue or feature you're working on.
  • Create a separate test_xyz.py script to accompany any changes you make, and document your tests (and any new code) clearly enough that they'll tell us everything we need to know about your rationale and implementation approach.
  • When you're ready and the new code passes all your tests, create a Pull Request from your Branch back to the Main Branch of this repository.

If you'd be kind enough to follow that approach it'll help speed things on their way and cause less brain-ache all round. Thank you, and we can't wait to hear people's ideas!

You can also get in contact on Twitter, and we're currently dabbling with the CodeStream extension for VS Code which seems to have some helpful collaborative features, so perhaps we can connect with that too?

5. CREDITS

Many thanks to the creators of the following awesome packages that easyPyPI makes use of 'under the bonnet':

  • PySimpleGUI- - used to built a nice interface that makes things even quicker and easier.
  • Click- - used to get the most suitable (platform specific) folder path for storing config.json.
  • MechanicalSoup- - used to automatically login to Github and create/push an initial repository.
  • Keyring- - used to store and retrieve account credentials securely.
  • pep440_version_utils- -- used to automatically upversion micro, minor, and major version numbers.
  • Twine - the "Go To" utility for uploading packages securely to PyPI and Test PyPI.

6. PAYING IT FORWARD

If easyPyPI helps you save time and focus on more important things, please show your appreciation by at least starring this repository on Github or even better:

Buy Me A Coffee

Yummy - thank you!

easypypi's People

Contributors

pfython avatar felixthec avatar jaykayace avatar

Stargazers

 avatar Shangjie Lyu avatar  avatar Christian Richter avatar Takehiro Ogura avatar Ceyhun Derinbogaz avatar zazum3 avatar  avatar La Min Ko avatar fabio bonfanti avatar  avatar PySimpleGUI avatar  avatar  avatar

Watchers

James Cloos avatar  avatar  avatar PySimpleGUI avatar

Forkers

jaykayace

easypypi's Issues

Single Window Design

I haven’t really used easypypi, but had a glimpse at it. It all looks impressive.

I was surprised to see that you had organized the UI as a linear number of lists. That has the disadvantage that you have to go through ALL questions and that you can’t go back and don’t have an overview.
Would it be much nicer to have a window with all the prompts, where you can just what you want.

In order to demonstrate what I mean, I have made a mockup:
image
image
763×397 15.9 KB

Please observe that the checkbox options are kind of special. The fields are actually Text elements that trigger another window with the checkboxes:
image
image
765×399 14.6 KB
Here’s the -not very elegant- code of the mockup:

import PySimpleGUI as sg

def prompt_with_checkboxes(group, choices, selected_choices):
"""
Creates a scrollable checkbox popup using PySimpleGui
Returns a set of selected choices, or and empty set
"""
prompt = [sg.Text(text=f"Please select any relevant classifiers in the {group.title()} group:")]
layout = [[sg.Checkbox(text=choice, key=choice, default=choice in selected_choices)] for choice in choices]
event, values = sg.Window(
"", [prompt, [sg.Column(layout)], [sg.Button("Accept"), sg.Button("Cancel")]], resizable=True, no_titlebar=True
).read(close=True)

if event == "Accept":
    selected_choices.clear()
    selected_choices.extend(k for k in choices if values[k])
    return True
if event is None or event == "Cancel":
    return False

sg.theme("DarkAmber")
prompts = {
"name": "Name for this package (all lowercase, underscores if needed)",
"version": "Latest version number",
"github_id": "Your Github or main repository ID",
"url": "Link to the package repository",
"description": "Description with escape characters for \ " ' etc.",
"author": "Full name of the author",
"email": "E-mail address for the author",
"keywords": "Some keywords separated by a comma",
"requirements": "Any packages/modules that absolutely need to be installed",
}
answer = {}
answer["name"] = "salabim"
answer["version"] = "20.0.6"
answer["github_id"] = "www.ggithub.com/salabim/salabim"
answer["url"] = "www.salabim.org"
answer["description"] = "Discrete event simulation in Python"
answer["author"] = "Ruud van der Ham"
answer["email"] = "[email protected]"
answer["keywords"] = "simulation,engineering,logistics"
answer["requirements"] = ""

layout = []
for key, prompt in prompts.items():

layout += [[sg.Text(prompt, size=(50, 0)), sg.Input(answer[key], key=key, size=(40, 0))]]

choices = {}
selected_choices = {}
for group in "Development Status|Intended Audience|Operating System|Programming Language :: |Topic".split("|"):
choices[group] = [option for option in "Option 1|Option 2|Option 3|Option 4|Option 5|Option 6|Option 7|Option 8".split("|")]
selected_choices[group] = []
if group == "Programming Language :: ":
selected_choices[group] = "Option 3|Option 4|Option 5|Option 7|Option 8".split("|")
layout += [
[
sg.Text(group, size=(50, 1)),
sg.Text(
", ".join(selected_choices[group]),
key=("group", group),
enable_events=True,
size=(40, 0),
background_color=sg.theme_input_background_color(),
text_color=sg.theme_text_color(),
),
]
]

window = sg.Window("easypypi", layout)
while True:
event, values = window.read()
if event is None:
break

print(event)
if isinstance(event, tuple):
    group = event[1]
    prompt_with_checkboxes(group, choices=choices[group], selected_choices=selected_choices[group])
    window[event].update(value=", ".join(selected_choices[group]))

What do you think of that?

Capture all stdout/stderr output

start_gui(redirect=True) captures some but not all output currently... e.g. the output from running setup.py, twine, and pip.

I'm not familiar enough with PySimpleGUI and generally handling stdout/stderr to solve this on my own I'm afraid...

Review and improve get_next_version()

I'm not 100% confident the current upversioning function follows best practise, and it would also be good to other schemas e.g. date format: 2020.21.11 which is used by quite a few authors.

Pre-select checkboxes and radio buttons

Classifier data is currently being lost because easyPyPI doesn't pre-select checkboxes (or radio button in the case of License selection). If the user selects nothing or cancels at this point, self.classifiers is reset to nothing which is annoying if you've taken time and care choosing exactly the right classifiers previously!

This issue is linked to #2

Save Passwords securely

At the moment users have to enter passwords for PyPI and TestPyPI every time the create a new Package.

Would be nice to remember these passwords securely e.g. using keyring but the the author currently has no experience of this.

Breaks on review() with version number

Because of a decimal.conversion issue, package.version cannot take a third number (e.g., 0.13.1):

In [25]: package.review()
---------------------------------------------------------------------------
InvalidOperation                          Traceback (most recent call last)
<ipython-input-25-b14de665a1fc> in <module>
----> 1 package.review()

c:\users\gil\envs\utils\lib\site-packages\easypypi\easypypi.py in review(self)
    203         """
    204         self.check_account_credentials("github_")  # sets self.github_username
--> 205         self.get_metadata()
    206         self.get_license()
    207         self.get_classifiers()

c:\users\gil\envs\utils\lib\site-packages\easypypi\easypypi.py in get_metadata(self)
    326                     pass
    327             if key == "version" and self.get("version"):
--> 328                 default = self.next_version
    329             new = sg.popup_get_text(prompt, default_text=default, **sg_kwargs)
    330             if new is None:

c:\users\gil\envs\utils\lib\site-packages\easypypi\easypypi.py in next_version(self)
    687     def next_version(self):
    688         """ Suggests next package version number based on simple schemas """
--> 689         decimal_version = decimal(str(self.version))
    690         try:
    691             _, digits, exponent = decimal_version.as_tuple()

InvalidOperation: [<class 'decimal.ConversionSyntax'>]

In [26]: %debug
> c:\users\gil\envs\utils\lib\site-packages\easypypi\easypypi.py(689)next_version()
    687     def next_version(self):
    688         """ Suggests next package version number based on simple schemas """
--> 689         decimal_version = decimal(str(self.version))
    690         try:
    691             _, digits, exponent = decimal_version.as_tuple()

ipdb> self.version
'0.13.1'

In [29]: package.version = '0.13'

Changing version to only Major.Minor in the last line [29] above solved the issue, but you might want to enable longer version numbers (PEP440).

Accept/Cancel buttons hidden until scrolled to end of list

I'm struggling with the following code... At the moment the Accept and Cancel buttons are not visible until you scroll all the way down through every checkbox option. Can you see an easy way to make ONLY the checkboxes scrollable, and have the buttons always visible at the bottom of the window?

[cleversetup] easypypi/easypypi.py (Lines 920-964)


def prompt_with_choices(group, choices, selected_choices):
    """
    Creates a scrollable popup using PySimpleGui checkboxes or radio buttons.
    Returns a set of selected choices, or and empty set
    """
    if group in ["Development Status", "License :: OSI Approved ::"]:
        layout = [
            [
                sg.Radio(
                    text=choice,
                    group_id=group,
                    key=choice,
                    default=choice in selected_choices,
                )
            ]
            for choice in choices
        ]
    else:
        layout = [
            [sg.Checkbox(text=choice, key=choice, default=choice in selected_choices)]
            for choice in choices
        ]
    buttons = [sg.Button("Accept"), sg.Button("Cancel")]
    if group == "License :: OSI Approved ::":
        buttons += [sg.Button("License Help")]
    choices_window = sg.Window(
        f"Classifiers for the {group.title()} group",
        [
            "",
            [
                sg.Column(
                    layout + [buttons],
                    scrollable=True,
                    vertical_scroll_only=True,
                    size=(600, 300),
                )
            ],
        ],
        size=(600, 300),
        resizable=True,
        keep_on_top=SG_KWARGS["keep_on_top"],
        icon=SG_KWARGS["icon"],
    )
    while True:
        event, values = choices_window.read(close=False)

Open in IDE · Open on GitHub

Not all input fields update dynamically

For example entering a new package Name will not restart the creation process; changing the Github username will not change the URL or credentials held in keyring.

These limitations aren't obvious or problematic for creating a single package in a single session. Current workaround is to simply close the window and create a new package=Package(), but it would be nice for this to happen more dynamically.

The author doesn't plan to make further cosmetic/navigation/UX/UI improvements unfortunately, so if anyone with competent PySimpleGUI skills is reading this and has the time and inclination to create improvements that would be fantastic, thank you.

[cleversetup] easypypi/easypypi.py (Lines 513-534)


    def get_user_input(self):
        """
        Check config file for previous values.  If no value is set, prompts for
        a value and updates the relevant Package attribute.
        """
        layout = self.get_main_layout_inputs()
        layout, choices, selected_choices = self.get_main_layout_classifiers(layout)
        layout = self.get_main_layout_buttons(layout)
        window = sg.Window(
            "easyPyPI",
            layout,
            keep_on_top=SG_KWARGS["keep_on_top"],
            icon=SG_KWARGS["icon"],
            element_justification="center",
        )
        while True:
            set_menu_colours(window)
            event, values = window.read()
            if event is None:
                window.close()
                return False
            if event ==

Open in IDE · Open on GitHub

get version from the source file

Some people (at least me) use another way to version on PyPI.
In that case, the version is retrieved from the source file and used in PyPI. It would be nice if easypypi could accommodate thus scheme as well.
To make thing complicated, this makes it required to add a subversion if the version is already used. I have a prepare.py program to patch the setup.py file. It's not very generic, though.
Let's discuss.

Auto detect/install Git?

upload_to_github() uses Git system commands but obviously needs Git installed first.

For ultimate convenience, maybe include the option to auto install Git (e.g. for newcomers, or setting up on a new computer?).

At the very least, auto-detect / handle error and direct user to the right website.

Alternatively, I wonder if there's a Python package out there which gives programmatic access to Git functions without having Git CLI installed?

MenuButton colours don't take effect immediately

I found this 'workaround' for setting the colours of the MenuButton menu items (not the button itself) but they only seem to take effect after other GUI events have been triggered, not from the moment the window is created. This gives the effect of colours apparently changing randomly from default white background to the desired theme colours...

[cleversetup] easypypi/easypypi.py (Lines 900-917)



def set_menu_colours(window):
    """ Sets the colours of MenuButton menu options """
    background = "#2c2825"
    foreground = "#fdcb52"
    try:
        for menu in [
            "1) Upversion",
            "3) Publish",
            "Create Accounts",
            "Browse Files",
        ]:
            window[menu].TKMenu.configure(
                fg=foreground,
                bg=background,
            )
    except:
        pass  # This workaround attempts to change a non-existent menu

Open in IDE · Open on GitHub

Autogenerate tests from README code examples

READMEs will often include code to demonstrate the intended behaviour of a script. A great utility would be to automatically convert code snippets into outline tests, or at least an 80% version where all the tedious formatting and copy/pasting has been avoided.

For example:

README:

## 1. ATTRIBUTE NAMES

>>> x = CleverDict(значение = "znacheniyeh: Russian word for 'value'")
>>> x.значение
"znacheniyeh: Russian word for 'value'"

## 2. ERRORS
>>> x.nonexistent
AttributeError: 'nonexistent'

TEST

class test_README_code_examples:
    def test_ATTRIBUTE_NAMES_1(self):
        """ Code Example 1 from section: 1. ATTRIBUTE NAMES of the README """
        x = CleverDict(значение = "znacheniyeh: Russian word for 'value'")
        assert  x.значение == "znacheniyeh: Russian word for 'value'"
    def test_ATTRIBUTE_NAMES_1(self):
        """ Code Example 1 from section 2. ATTRIBUTE NAMES of the README """
        with pytest.raises(AttributeError):
            x.nonexistent

OUTLINE APPROACH

import pyperclip
lines = pyperclip.paste()
lines1 = [x for x in lines.split("\n") if "    " in x]
lines2 = [x.replace('    >>> x\r', 'assert x == "') for x in lines1]
lines3 = [x.replace('>>> ', '') for x in lines2]
lines4 = ["    "+x for x in lines3]
# macro for inserting "assert ", going to end of line, deleting \n and subsequent 8(?) spaces etc.
pyperclip.copy(lines4)  # or actually append to test.py 

License APPENDS to itself

In certain circumstances (I just noticed it once...) the LICENSE file Appends rather than overwrites itself, resulting in multiple copies of the same License body text.

Get version from source file

Some people (at least me) use another way to version on PyPI.
In that case, the version is retrieved from the source file and used in PyPI. It would be nice if easypypi could accommodate thus scheme as well.
To make thing complicated, this makes it required to add a subversion if the version is already used. I have a prepare.py program to patch the setup.py file. It's not very generic, though.
Let's discuss.

Add Git push to 3) Publish button menu

easyPyPI helps new Pythonistas by automatically creating and initialising a Github Repository. The author doesn't use git commands usually and just pasted in the recommended incantations from Github itself:

def publish_to_github(self):
    """ Uploads initial package to Github using Git"""
    if not self.get_username("Github"):
        return False
    commands = f"""
    git init
    git add *.*
    git commit -m "Committing version {self.version}"
    git branch -M main
    git remote add origin https://github.com/{self.Github_username}/{self.name}.git
    git push -u origin main
    """
    choice = sg.popup_yes_no(
        f"Do you want to upload (Push) your package to Github?\n\n ⚠   CAUTION - "
        f"Only recommended when creating your repository for the first time!  "
        f"This automation requires Git and will run the following commands:\n\n{commands}",
        **SG_KWARGS,
    )
    if choice != "Yes":
        return False
    os.chdir(self.setup_filepath.parent)
    for command in commands.splitlines()[1:]:  # Ignore first blank line
        if not os.system(f"cmd /c {command}"):
            # A return value of 1 indicates an error, 0 indicates success
            print(f"\n ⓘ  Your package is now online at:\n  {self.url}':\n")

Although easyPyPI isn't intended to replace the git or Github client or the numerous plugins and extensions to IDEs that help with version control, it might be a nice convenience function to add the ability to Push the latest Repository to Github immediately after publishing on PyPI.

If anyone can help with the correct git incantations and a little logic to check things are set up correctly ready for the Push, please do contribute and send a Pull Request! Many thanks :)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.