Merge pull request #4667 from PySimpleGUI/Dev-latest

NEW GitHub Upgrade algorithm and window with status. BIG thank you to…
This commit is contained in:
PySimpleGUI 2021-08-27 18:05:09 -04:00 committed by GitHub
commit 52cdfca740
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 141 additions and 172 deletions

View File

@ -1,6 +1,6 @@
#!/usr/bin/python3
version = __version__ = "4.46.0.11 Unreleased"
version = __version__ = "4.46.0.12 Unreleased"
"""
Changelog since 4.46.0 release to PyPI on 10 Aug 2021
@ -31,7 +31,9 @@ version = __version__ = "4.46.0.11 Unreleased"
4.46.0.11
Another tuple / int convenience change. Tired of typing pad=(0,0)? Yea, me too. Now we can type pad=0.
If an int is specified instead of a typle, then a tuple will be created to be same as the int ---> (int, int)
4.46.0.12
Add NEW upgrade from GitHub code. Thank you @israel-dryer!
Fix for Image.update docstring
"""
__version__ = version.split()[0] # For PEP 396 and PEP 345
@ -193,16 +195,17 @@ except:
webbrowser_available = False
# used for github upgrades
import sys
import site
import shutil
import hashlib
import base64
import glob
import configparser
import urllib.request
import urllib.error
import urllib.parse
from urllib import request
import os
import sys
import re
import site
import tempfile
warnings.simplefilter('always', UserWarning)
g_time_start = 0
@ -857,6 +860,8 @@ class Element():
if size is not None:
if isinstance(size, int):
size = (size, 1)
if isinstance(size, tuple) and len(size) == 1:
size = (size[0], 1)
if pad is not None:
if isinstance(pad, int):
@ -3104,9 +3109,9 @@ class Text(Element):
:param text: The text to display. Can include /n to achieve multiple lines. Will convert (optional) parameter into a string
:type text: Any
:param size: (width, height) width = characters-wide, height = rows-high
:type size: (int, int) | (int, None) | (None, None) | int
:type size: (int, int) | (int, None) | (None, None) | (int, ) | int
:param s: Same as size parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, size will be used
:type s: (int, int) | (int, None) | (None, None) | int
:type s: (int, int) | (int, None) | (None, None) | (int, ) | int
:param auto_size_text: if True size of the Text Element will be sized to fit the string provided in 'text' parm
:type auto_size_text: (bool)
:param click_submits: DO NOT USE. Only listed for backwards compat - Use enable_events instead
@ -4655,7 +4660,7 @@ class Image(Element):
:type filename: (str)
:param data: Base64 encoded string OR a tk.PhotoImage object
:type data: str | tkPhotoImage
:param size: size of a image (w,h) w=characters-wide, h=rows-high
:param size: (width, height) size of image in pixels
:type size: Tuple[int,int]
:param visible: control visibility of element
:type visible: (bool)
@ -20873,182 +20878,145 @@ def main_open_github_issue():
window.close()
def _copy_files_from_github(files, github_url=None):
'''
MM'"""""`MM oo dP M""MMMMM""MM dP
M' .mmm. `M 88 M MMMMM MM 88
M MMMMMMMM dP d8888P M `M dP dP 88d888b.
M MMM `M 88 88 M MMMMM MM 88 88 88' `88
M. `MMM' .M 88 88 M MMMMM MM 88. .88 88. .88
MM. .MM dP dP M MMMMM MM `88888P' 88Y8888'
MMMMMMMMMMM MMMMMMMMMMMM
M""MMMMM""M dP
M MMMMM M 88
M MMMMM M 88d888b. .d8888b. 88d888b. .d8888b. .d888b88 .d8888b.
M MMMMM M 88' `88 88' `88 88' `88 88' `88 88' `88 88ooood8
M `MMM' M 88. .88 88. .88 88 88. .88 88. .88 88. ...
Mb dM 88Y888P' `8888P88 dP `88888P8 `88888P8 `88888P'
MMMMMMMMMMM 88 .88
dP d8888P
'''
'''
M""""""""M dP dP
Mmmm mmmM 88 88
MMMM MMMM 88d888b. 88d888b. .d8888b. .d8888b. .d888b88
MMMM MMMM 88' `88 88' `88 88ooood8 88' `88 88' `88
MMMM MMMM 88 88 88 88. ... 88. .88 88. .88
MMMM MMMM dP dP dP `88888P' `88888P8 `88888P8
MMMMMMMMMM
'''
def _the_github_upgrade_thread(window, sp):
"""
install one file package from GitHub or current directory
Parameters
----------
files : list
files to be installed
the first item (files[0]) will be used as the name of the package''
optional files should be preceded with an exclamation mark (!)
github_url : str
url of the location of the GitHub repository
this will start usually with https://raw.githubusercontent.com/ and end with /master/
if omitted, the files will be copied from the current directory (not GitHub)
Returns
-------
info : Info instance
info.package : name of the package installed
info.path : name where the package is installed in the site-packages
info.version : version of the package (obtained from <package>.py)
info.files_copied : list of copied files
Notes
-----
The program automatically makes the required __init__.py file (unless given in files) and
<package><version>.dist-info folder with the usual files METADATA, INSTALLER and RECORDS.
As the setup.py is not run, the METADATA is very limited, i.e. is contains just name and version.
If a __init__.py is in files that file will be used.
Otherwise, an __init__/py file will be generated. In thet case, if a __version__ = statement
is found in the source file, the __version__ will be included in that __init__.py file.
The thread that's used to run the subprocess so that the GUI can continue and the stdout/stderror is collected
:param window:
:param sp:
:return:
"""
class _ReturnInfo:
src = ""
package = ""
new_files = ""
path = ""
version = ""
window.write_event_value('-THREAD-', (sp, '===THEAD STARTING==='))
window.write_event_value('-THREAD-', (sp, '----- STDOUT Follows ----'))
for line in sp.stdout:
oline = line.decode().rstrip()
window.write_event_value('-THREAD-', (sp, oline))
window.write_event_value('-THREAD-', (sp, '----- STDERR ----'))
def path_stem(path):
head, tail = os.path.split(path)
retval = tail or os.path.basename(head)
return os.path.splitext(retval)[0]
for line in sp.stderr:
oline = line.decode().rstrip()
window.write_event_value('-THREAD-', (sp, oline))
window.write_event_value('-THREAD-', (sp, '===THEAD DONE==='))
info = _ReturnInfo()
info.src = files[0]
info.package = path_stem(files[0])
page_contents = {}
for f in files:
is_file_optional = f[0] == "!"
if (is_file_optional):
f = f[1:]
if (os.path.exists(f)):
with urllib.request.urlopen(github_url + f) as resp:
page = resp.read()
page_contents[f] = page
else:
with urllib.request.urlopen(github_url + f) as resp:
page = resp.read()
page_contents[f] = page
version = "?"
for line in page_contents[info.src].decode("utf-8").split("\n"):
line_split = line.split("__version__ =")
if len(line_split) > 1:
raw_version = line_split[-1].strip(" '\"")
version = ""
for c in raw_version:
if c in "0123456789-.":
version += c
else:
break
def _copy_files_from_github():
"""Update the local PySimpleGUI installation from Github"""
github_url = 'https://raw.githubusercontent.com/PySimpleGUI/PySimpleGUI/master/'
files = ["PySimpleGUI.py", "setup.py"]
# add a temp directory
temp_dir = tempfile.TemporaryDirectory()
path = temp_dir.name
# os.mkdir('temp')
# path = os.path.abspath('temp')
# download the files
downloaded = []
for file in files:
with request.urlopen(github_url + file) as response:
with open(os.path.join(path, file), 'wb') as f:
f.write(response.read())
downloaded.append(file)
# get the new version number if possible
with open(os.path.join(path, files[0]), encoding='utf-8') as f:
text_data = f.read()
package_version = "Unknown"
match = re.search(r'__version__ = \"([\d\.]+)', text_data)
if match:
package_version = match.group(1)
# update the setup.py file
with open(os.path.join(path, files[1]), encoding='utf-8') as f:
text_data = f.read()
with open(os.path.join(path, files[1]), 'w', encoding='utf-8') as f:
edit1 = re.sub("version.+", 'version="' + package_version + '",', text_data)
edit2 = re.sub("packages.+", r'packages=["."],', edit1)
f.write(edit2)
# create an __init__.py file
with open(os.path.join(path, '__init__.py'), 'w', encoding='utf-8') as f:
f.write('')
# install the pysimplegui package from local dist
# https://pip.pypa.io/en/stable/user_guide/?highlight=subprocess#using-pip-from-your-program
# subprocess.check_call([sys.executable, '-m', 'pip', 'install', path])
sp = execute_command_subprocess(sys.executable, '-m pip install', path, pipe_output=True)
layout = [[Text('Pip Upgrade Progress')],
[Multiline(s=(80,20), k='-MLINE-', reroute_cprint=True, write_only=True)],
[Button('Downloading...', k='-EXIT-')]]
window = Window('Pip Upgrade', layout, finalize=True, keep_on_top=True, modal=True, disable_close=True)
threading.Thread(target=_the_github_upgrade_thread, args=(window, sp), daemon=True).start()
while True:
event, values = window.read()
if event == WIN_CLOSED or (event == '-EXIT-' and window['-EXIT-'].ButtonText == 'Done'):
break
info.version = version
info.new_files = info.files_copied = list(page_contents.keys())
sitepackages_path = ""
if "__init__.py" not in page_contents:
page_contents["__init__.py"] = ("from ." + info.package + " import *\n").encode()
if version != "unknown":
page_contents["__init__.py"] += ("from ." + info.package + " import __version__\n").encode()
if running_linux() or running_mac():
dir_search = sys.path
else:
dir_search = site.getsitepackages()
if event == '-THREAD-':
cprint(values['-THREAD-'][1])
if values['-THREAD-'][1] == '===THEAD DONE===':
window['-EXIT-'].update(text='Done', button_color='white on red')
window.close()
# cleanup and remove files
temp_dir.cleanup()
for f in dir_search:
if ((os.path.isdir(f)) and (str("site-packages") in str(f))):
sitepackages_path = f
break
else:
raise ModuleNotFoundError("Unable to find site-packages folder!")
# return metadata
try:
mod_path = site.getsitepackages()[0]
except IndexError:
mod_path = ''
path = os.path.join(str(sitepackages_path), str(info.package))
info.path = str(path)
if (os.path.isfile(path)):
os.unlink(path)
if not os.path.isdir(path):
os.mkdir(path)
for file, contents in page_contents.items():
with open(os.path.join(str(path), str(file)), "wb") as f:
f.write(contents)
if running_mac():
pypi_packages = str(sitepackages_path) + "/.pypi_packages"
config = configparser.ConfigParser()
config.read(pypi_packages)
config[info.package] = {}
config[info.package]["github_url"] = "github"
config[info.package]["version"] = version
config[info.package]["summary"] = ""
config[info.package]["files"] = path.as_posix()
config[info.package]["dependency"] = ""
with open(pypi_packages, "w") as f:
config.write(f)
else:
for entry in glob.glob(sitepackages_path + "/*"):
if os.path.isdir(entry):
if path_stem(entry).startswith(info.package + "-") and ".dist-info" in entry:
shutil.rmtree(entry)
path_distinfo = str(path) + "-" + str(version) + ".dist-info"
if not os.path.isdir(path_distinfo):
os.mkdir(path_distinfo)
with open(path_distinfo + "/METADATA", "w") as f:
f.write("Name: " + info.package + "\n")
f.write("Version: " + version + "\n")
with open(path_distinfo + "/INSTALLER", "w") as f:
f.write("github\n")
with open(path_distinfo + "/RECORD", "w") as f:
pass
with open(str(path_distinfo) + "/RECORD", "w") as record_file:
for p in (path, path_distinfo):
for file in glob.glob(str(p) + "**/*"):
if os.path.isfile(file):
name = os.path.join(sitepackages_path, file) # make sure we have slashes
record_file.write(name + ",")
if (path_stem(file) == "RECORD" and p == path_distinfo) or ("__pycache__" in name.lower()):
record_file.write(",")
else:
with open(file, "rb") as f:
file_contents = f.read()
hash = "sha256=" + base64.urlsafe_b64encode(
hashlib.sha256(file_contents).digest()
).decode("latin1").rstrip("=")
# hash calculation derived from wheel.py in pip
length = str(len(file_contents))
record_file.write(hash + "," + length)
record_file.write("\n")
return info
return package_version, mod_path or ''
def _upgrade_from_github():
info = _copy_files_from_github(
files="PySimpleGUI.py !init.py".split(), github_url="https://raw.githubusercontent.com/PySimpleGUI/PySimpleGUI/master/"
)
# print(info.package + " " + info.version + " successfully installed in " + info.path)
# print("files copied: ", ", ".join(info.files_copied))
mod_version, mod_path = _copy_files_from_github()
popup("*** SUCCESS ***", info.package, info.version, "successfully installed in ", info.path, "files copied: ", info.files_copied, keep_on_top=True,
background_color='red', text_color='white')
popup("*** SUCCESS ***", "PySimpleGUI", mod_version,
"successfully installed in ", mod_path, "files copied: ",
"PySimpleGUI.py", keep_on_top=True, background_color='red',
text_color='white')
def _upgrade_gui():
@ -21060,6 +21028,7 @@ def _upgrade_gui():
if popup_yes_no('* WARNING *',
'You are about to upgrade your PySimpleGUI package previously installed via pip to the latest version location on the GitHub server.',
'You are running verrsion {}'.format(cur_ver),
'',
'Are you sure you want to overwrite this release?', title='Are you sure you want to overwrite?',
keep_on_top=True) == 'Yes':
_upgrade_from_github()