##// END OF EJS Templates
fix long description
Matthias Bussonnier -
Show More
@@ -0,0 +1,37 b''
1 IPython provides a rich toolkit to help you make the most out of using Python
2 interactively. Its main components are:
3
4 * A powerful interactive Python shell
5 * A `Jupyter <https://jupyter.org/>`_ kernel to work with Python code in Jupyter
6 notebooks and other interactive frontends.
7
8 The enhanced interactive Python shells have the following main features:
9
10 * Comprehensive object introspection.
11
12 * Input history, persistent across sessions.
13
14 * Caching of output results during a session with automatically generated
15 references.
16
17 * Extensible tab completion, with support by default for completion of python
18 variables and keywords, filenames and function keywords.
19
20 * Extensible system of 'magic' commands for controlling the environment and
21 performing many tasks related either to IPython or the operating system.
22
23 * A rich configuration system with easy switching between different setups
24 (simpler than changing $PYTHONSTARTUP environment variables every time).
25
26 * Session logging and reloading.
27
28 * Extensible syntax processing for special purpose situations.
29
30 * Access to the system shell with user-extensible alias system.
31
32 * Easily embeddable in other Python programs and GUIs.
33
34 * Integrated access to the pdb debugger and the Python profiler.
35
36 The latest development version is always available from IPython's `GitHub
37 site <http://github.com/ipython>`_.
@@ -1,47 +1,48 b''
1 include README.rst
1 include README.rst
2 include COPYING.rst
2 include COPYING.rst
3 include LICENSE
3 include LICENSE
4 include setupbase.py
4 include setupbase.py
5 include MANIFEST.in
5 include MANIFEST.in
6 include pytest.ini
6 include pytest.ini
7 include mypy.ini
7 include mypy.ini
8 include .mailmap
8 include .mailmap
9 include .flake8
9 include .flake8
10 include .pre-commit-config.yaml
10 include .pre-commit-config.yaml
11 include long_description.rst
11
12
12 recursive-exclude tools *
13 recursive-exclude tools *
13 exclude tools
14 exclude tools
14 exclude CONTRIBUTING.md
15 exclude CONTRIBUTING.md
15 exclude .editorconfig
16 exclude .editorconfig
16
17
17 graft setupext
18 graft setupext
18
19
19 graft scripts
20 graft scripts
20
21
21 # Load main dir but exclude things we don't want in the distro
22 # Load main dir but exclude things we don't want in the distro
22 graft IPython
23 graft IPython
23
24
24 # Documentation
25 # Documentation
25 graft docs
26 graft docs
26 exclude docs/\#*
27 exclude docs/\#*
27 exclude docs/man/*.1.gz
28 exclude docs/man/*.1.gz
28
29
29 exclude .git-blame-ignore-revs
30 exclude .git-blame-ignore-revs
30
31
31 # Examples
32 # Examples
32 graft examples
33 graft examples
33
34
34 # docs subdirs we want to skip
35 # docs subdirs we want to skip
35 prune docs/build
36 prune docs/build
36 prune docs/gh-pages
37 prune docs/gh-pages
37 prune docs/dist
38 prune docs/dist
38
39
39 # Patterns to exclude from any directory
40 # Patterns to exclude from any directory
40 global-exclude *~
41 global-exclude *~
41 global-exclude *.flc
42 global-exclude *.flc
42 global-exclude *.yml
43 global-exclude *.yml
43 global-exclude *.pyc
44 global-exclude *.pyc
44 global-exclude *.pyo
45 global-exclude *.pyo
45 global-exclude .dircopy.log
46 global-exclude .dircopy.log
46 global-exclude .git
47 global-exclude .git
47 global-exclude .ipynb_checkpoints
48 global-exclude .ipynb_checkpoints
@@ -1,108 +1,71 b''
1 [metadata]
1 [metadata]
2 name = ipython
2 name = ipython
3 version = attr: IPython.core.release.__version__
3 version = attr: IPython.core.release.__version__
4 url = https://ipython.org
4 url = https://ipython.org
5 description = IPython: Productive Interactive Computing
5 description = IPython: Productive Interactive Computing
6 long_description_content_type = text/x-rst
6 long_description_content_type = text/x-rst
7 long_description = IPython provides a rich toolkit to help you make the most out of using Python
7 long_description = file: long_description.rst
8 interactively. Its main components are:
9
10 * A powerful interactive Python shell
11 * A `Jupyter <https://jupyter.org/>`_ kernel to work with Python code in Jupyter
12 notebooks and other interactive frontends.
13
14 The enhanced interactive Python shells have the following main features:
15
16 * Comprehensive object introspection.
17
18 * Input history, persistent across sessions.
19
20 * Caching of output results during a session with automatically generated
21 references.
22
23 * Extensible tab completion, with support by default for completion of python
24 variables and keywords, filenames and function keywords.
25
26 * Extensible system of 'magic' commands for controlling the environment and
27 performing many tasks related either to IPython or the operating system.
28
29 * A rich configuration system with easy switching between different setups
30 (simpler than changing $PYTHONSTARTUP environment variables every time).
31
32 * Session logging and reloading.
33
34 * Extensible syntax processing for special purpose situations.
35
36 * Access to the system shell with user-extensible alias system.
37
38 * Easily embeddable in other Python programs and GUIs.
39
40 * Integrated access to the pdb debugger and the Python profiler.
41
42 The latest development version is always available from IPython's `GitHub
43 site <http://github.com/ipython>`_.
44
45 license_file = LICENSE
8 license_file = LICENSE
46 project_urls =
9 project_urls =
47 Documentation = https://ipython.readthedocs.io/
10 Documentation = https://ipython.readthedocs.io/
48 Funding = https://numfocus.org/
11 Funding = https://numfocus.org/
49 Source = https://github.com/ipython/ipython
12 Source = https://github.com/ipython/ipython
50 Tracker = https://github.com/ipython/ipython/issues
13 Tracker = https://github.com/ipython/ipython/issues
51 keywords = Interactive, Interpreter, Shell, Embedding
14 keywords = Interactive, Interpreter, Shell, Embedding
52 platforms = Linux, Mac OSX, Windows
15 platforms = Linux, Mac OSX, Windows
53 classifiers =
16 classifiers =
54 Framework :: IPython
17 Framework :: IPython
55 Intended Audience :: Developers
18 Intended Audience :: Developers
56 Intended Audience :: Science/Research
19 Intended Audience :: Science/Research
57 License :: OSI Approved :: BSD License
20 License :: OSI Approved :: BSD License
58 Programming Language :: Python
21 Programming Language :: Python
59 Programming Language :: Python :: 3
22 Programming Language :: Python :: 3
60 Programming Language :: Python :: 3 :: Only
23 Programming Language :: Python :: 3 :: Only
61 Topic :: System :: Shells
24 Topic :: System :: Shells
62
25
63
26
64 [options]
27 [options]
65 packages = find:
28 packages = find:
66 python_requires = >=3.8
29 python_requires = >=3.8
67 zip_safe = False
30 zip_safe = False
68 install_requires =
31 install_requires =
69 setuptools>=18.5
32 setuptools>=18.5
70 jedi>=0.16
33 jedi>=0.16
71 decorator
34 decorator
72 pickleshare
35 pickleshare
73 traitlets>=5
36 traitlets>=5
74 prompt_toolkit>=2.0.0,<3.1.0,!=3.0.0,!=3.0.1
37 prompt_toolkit>=2.0.0,<3.1.0,!=3.0.0,!=3.0.1
75 pygments
38 pygments
76 backcall
39 backcall
77 stack_data
40 stack_data
78 matplotlib-inline
41 matplotlib-inline
79 pexpect>4.3; sys_platform != "win32"
42 pexpect>4.3; sys_platform != "win32"
80 appnope; sys_platform == "darwin"
43 appnope; sys_platform == "darwin"
81 colorama; sys_platform == "win32"
44 colorama; sys_platform == "win32"
82
45
83 [options.packages.find]
46 [options.packages.find]
84 exclude =
47 exclude =
85 setupext
48 setupext
86
49
87 [options.package_data]
50 [options.package_data]
88 IPython.core = profile/README*
51 IPython.core = profile/README*
89 IPython.core.tests = *.png, *.jpg, daft_extension/*.py
52 IPython.core.tests = *.png, *.jpg, daft_extension/*.py
90 IPython.lib.tests = *.wav
53 IPython.lib.tests = *.wav
91 IPython.testing.plugin = *.txt
54 IPython.testing.plugin = *.txt
92
55
93 [options.entry_points]
56 [options.entry_points]
94 console_scripts =
57 console_scripts =
95 ipython = IPython:start_ipython
58 ipython = IPython:start_ipython
96 ipython3 = IPython:start_ipython
59 ipython3 = IPython:start_ipython
97 pygments.lexers =
60 pygments.lexers =
98 ipythonconsole = IPython.lib.lexers:IPythonConsoleLexer
61 ipythonconsole = IPython.lib.lexers:IPythonConsoleLexer
99 ipython = IPython.lib.lexers:IPythonLexer
62 ipython = IPython.lib.lexers:IPythonLexer
100 ipython3 = IPython.lib.lexers:IPython3Lexer
63 ipython3 = IPython.lib.lexers:IPython3Lexer
101
64
102 [velin]
65 [velin]
103 ignore_patterns =
66 ignore_patterns =
104 IPython/core/tests
67 IPython/core/tests
105 IPython/testing
68 IPython/testing
106
69
107 [tool.black]
70 [tool.black]
108 exclude = 'timing\.py'
71 exclude = 'timing\.py'
@@ -1,23 +1,23 b''
1 #!/usr/bin/env python3
1 #!/usr/bin/env python3
2 """IPython release build script.
2 """IPython release build script.
3 """
3 """
4 import os
4 import os
5 import sys
5 import sys
6 from shutil import rmtree
6 from shutil import rmtree
7
7
8 from toollib import sh, pjoin, get_ipdir, cd, sdists, buildwheels
8 from toollib import sh, pjoin, get_ipdir, cd, sdists, buildwheels
9
9
10 def build_release():
10 def build_release():
11
11
12 # Get main ipython dir, this will raise if it doesn't pass some checks
12 # Get main ipython dir, this will raise if it doesn't pass some checks
13 ipdir = get_ipdir()
13 ipdir = get_ipdir()
14 cd(ipdir)
14 cd(ipdir)
15
15
16 # Build source and binary distros
16 # Build source and binary distros
17 sh(sdists)
17 sh(sdists)
18 buildwheels()
18 buildwheels()
19 # don't try to change, xz, bz2 deprecated.
19 sh(' '.join([sys.executable, 'tools/retar.py', 'dist/*.gz']))
20 sh(' '.join([sys.executable, 'tools/retar.py', 'dist/*.gz']))
20 sh(' '.join([sys.executable, 'tools/retar.py', 'dist/*.xz']))
21
21
22 if __name__ == '__main__':
22 if __name__ == '__main__':
23 build_release()
23 build_release()
@@ -1,93 +1,93 b''
1 #!/usr/bin/env python3
1 #!/usr/bin/env python3
2 """IPython release script.
2 """IPython release script.
3
3
4 This should ONLY be run at real release time.
4 This should ONLY be run at real release time.
5 """
5 """
6 from __future__ import print_function
6 from __future__ import print_function
7
7
8 import os
8 import os
9 from glob import glob
9 from glob import glob
10 from subprocess import call
10 from subprocess import call
11 import sys
11 import sys
12
12
13 from toollib import (get_ipdir, pjoin, cd, execfile, sh, archive,
13 from toollib import (get_ipdir, pjoin, cd, execfile, sh, archive,
14 sdists, archive_user, archive_dir, buildwheels)
14 sdists, archive_user, archive_dir, buildwheels)
15 from gh_api import post_download
15 from gh_api import post_download
16
16
17 # Get main ipython dir, this will raise if it doesn't pass some checks
17 # Get main ipython dir, this will raise if it doesn't pass some checks
18 ipdir = get_ipdir()
18 ipdir = get_ipdir()
19 tooldir = pjoin(ipdir, 'tools')
19 tooldir = pjoin(ipdir, 'tools')
20 distdir = pjoin(ipdir, 'dist')
20 distdir = pjoin(ipdir, 'dist')
21
21
22 # Where I keep static backups of each release
22 # Where I keep static backups of each release
23 ipbackupdir = os.path.expanduser('~/ipython/backup')
23 ipbackupdir = os.path.expanduser('~/ipython/backup')
24 if not os.path.exists(ipbackupdir):
24 if not os.path.exists(ipbackupdir):
25 os.makedirs(ipbackupdir)
25 os.makedirs(ipbackupdir)
26
26
27 # Start in main IPython dir
27 # Start in main IPython dir
28 cd(ipdir)
28 cd(ipdir)
29
29
30 # Load release info
30 # Load release info
31 version = None
31 version = None
32 execfile(pjoin('IPython','core','release.py'), globals())
32 execfile(pjoin('IPython','core','release.py'), globals())
33
33
34 # Build site addresses for file uploads
34 # Build site addresses for file uploads
35 release_site = '%s/release/%s' % (archive, version)
35 release_site = '%s/release/%s' % (archive, version)
36 backup_site = '%s/backup/' % archive
36 backup_site = '%s/backup/' % archive
37
37
38 # Start actual release process
38 # Start actual release process
39 print()
39 print()
40 print('Releasing IPython')
40 print('Releasing IPython')
41 print('=================')
41 print('=================')
42 print()
42 print()
43 print('Version:', version)
43 print('Version:', version)
44 print()
44 print()
45 print('Source IPython directory:', ipdir)
45 print('Source IPython directory:', ipdir)
46 print()
46 print()
47
47
48 # Perform local backup, go to tools dir to run it.
48 # Perform local backup, go to tools dir to run it.
49 cd(tooldir)
49 cd(tooldir)
50
50
51 if 'upload' in sys.argv:
51 if 'upload' in sys.argv:
52 cd(distdir)
52 cd(distdir)
53
53
54 # do not upload OS specific files like .DS_Store
54 # do not upload OS specific files like .DS_Store
55 to_upload = glob('*.whl')+glob('*.tar.gz')
55 to_upload = glob('*.whl')+glob('*.tar.gz')
56 for fname in to_upload:
56 for fname in to_upload:
57 # TODO: update to GitHub releases API
57 # TODO: update to GitHub releases API
58 continue
58 continue
59 print('uploading %s to GitHub' % fname)
59 print('uploading %s to GitHub' % fname)
60 desc = "IPython %s source distribution" % version
60 desc = "IPython %s source distribution" % version
61 post_download("ipython/ipython", fname, description=desc)
61 post_download("ipython/ipython", fname, description=desc)
62
62
63 # Make target dir if it doesn't exist
63 # Make target dir if it doesn't exist
64 print('1. Uploading IPython to archive.ipython.org')
64 print('1. Uploading IPython to archive.ipython.org')
65 sh('ssh %s "mkdir -p %s/release/%s" ' % (archive_user, archive_dir, version))
65 sh('ssh %s "mkdir -p %s/release/%s" ' % (archive_user, archive_dir, version))
66 sh('scp *.tar.gz *.tar.xz *.whl %s' % release_site)
66 sh('scp *.tar.gz *.tar.xz *.whl %s' % release_site)
67
67
68 print('2. Uploading backup files...')
68 print('2. Uploading backup files...')
69 cd(ipbackupdir)
69 cd(ipbackupdir)
70 sh('scp `ls -1tr *tgz | tail -1` %s' % backup_site)
70 sh('scp `ls -1tr *tgz | tail -1` %s' % backup_site)
71
71
72 print('3. Uploading to PyPI using twine')
72 print('3. Uploading to PyPI using twine')
73 cd(distdir)
73 cd(distdir)
74 call(['twine', 'upload'] + to_upload)
74 call(['twine', 'upload', '--verbose'] + to_upload)
75
75
76 else:
76 else:
77 # Build, but don't upload
77 # Build, but don't upload
78
78
79 # Make backup tarball
79 # Make backup tarball
80 sh('./make_tarball.py')
80 sh('./make_tarball.py')
81 sh('mv ipython-*.tgz %s' % ipbackupdir)
81 sh('mv ipython-*.tgz %s' % ipbackupdir)
82
82
83 # Build release files
83 # Build release files
84 sh('./build_release')
84 sh('./build_release')
85
85
86 cd(ipdir)
86 cd(ipdir)
87
87
88 buildwheels()
88 buildwheels()
89 print("`./release upload` to upload source distribution on PyPI and ipython archive")
89 print("`./release upload` to upload source distribution on PyPI and ipython archive")
90 sys.exit(0)
90 sys.exit(0)
91
91
92
92
93
93
@@ -1,83 +1,85 b''
1 """
1 """
2 Un-targz and retargz a targz file to ensure reproducible build.
2 Un-targz and retargz a targz file to ensure reproducible build.
3
3
4 usage:
4 usage:
5
5
6 $ export SOURCE_DATE_EPOCH=$(date +%s)
6 $ export SOURCE_DATE_EPOCH=$(date +%s)
7 ...
7 ...
8 $ python retar.py <tarfile.gz>
8 $ python retar.py <tarfile.gz>
9
9
10 The process of creating an sdist can be non-reproducible:
10 The process of creating an sdist can be non-reproducible:
11 - directory created during the process get a mtime of the creation date;
11 - directory created during the process get a mtime of the creation date;
12 - gziping files embed the timestamp of zip creation.
12 - gziping files embed the timestamp of zip creation.
13
13
14 This will untar-retar; ensuring that all mtime > SOURCE_DATE_EPOCH will be set
14 This will untar-retar; ensuring that all mtime > SOURCE_DATE_EPOCH will be set
15 equal to SOURCE_DATE_EPOCH.
15 equal to SOURCE_DATE_EPOCH.
16
16
17 """
17 """
18
18
19 import tarfile
19 import tarfile
20 import sys
20 import sys
21 import os
21 import os
22 import gzip
22 import gzip
23 import io
23 import io
24
24
25 from pathlib import Path
25 from pathlib import Path
26
26
27 if len(sys.argv) > 2:
27 if len(sys.argv) > 2:
28 raise ValueError("Too many arguments")
28 raise ValueError("Too many arguments")
29
29
30
30
31 timestamp = int(os.environ["SOURCE_DATE_EPOCH"])
31 timestamp = int(os.environ["SOURCE_DATE_EPOCH"])
32
32
33 path = Path(sys.argv[1])
33 path = Path(sys.argv[1])
34 old_buf = io.BytesIO()
34 old_buf = io.BytesIO()
35 with open(path, "rb") as f:
35 with open(path, "rb") as f:
36 old_buf.write(f.read())
36 old_buf.write(f.read())
37 old_buf.seek(0)
37 old_buf.seek(0)
38 if path.name.endswith("gz"):
38 if path.name.endswith("gz"):
39 r_mode = "r:gz"
39 r_mode = "r:gz"
40 if path.name.endswith(("xz", "xz2")):
40 if path.name.endswith("bz2"):
41 r_mode = "r:xz"
41 r_mode = "r:bz2"
42 if path.name.endswith("xz"):
43 raise ValueError("XZ is deprecated but it's written nowhere")
42 old = tarfile.open(fileobj=old_buf, mode=r_mode)
44 old = tarfile.open(fileobj=old_buf, mode=r_mode)
43
45
44 buf = io.BytesIO()
46 buf = io.BytesIO()
45 new = tarfile.open(fileobj=buf, mode="w", format=tarfile.GNU_FORMAT)
47 new = tarfile.open(fileobj=buf, mode="w", format=tarfile.GNU_FORMAT)
46 for i, m in enumerate(old):
48 for i, m in enumerate(old):
47 data = None
49 data = None
48 # mutation does not work, copy
50 # mutation does not work, copy
49 if m.name.endswith('.DS_Store'):
51 if m.name.endswith('.DS_Store'):
50 continue
52 continue
51 m2 = tarfile.TarInfo(m.name)
53 m2 = tarfile.TarInfo(m.name)
52 m2.mtime = min(timestamp, m.mtime)
54 m2.mtime = min(timestamp, m.mtime)
53 m2.pax_headers["mtime"] = m2.mtime
55 m2.pax_headers["mtime"] = m2.mtime
54 m2.size = m.size
56 m2.size = m.size
55 m2.type = m.type
57 m2.type = m.type
56 m2.linkname = m.linkname
58 m2.linkname = m.linkname
57 m2.mode = m.mode
59 m2.mode = m.mode
58 if m.isdir():
60 if m.isdir():
59 new.addfile(m2)
61 new.addfile(m2)
60 else:
62 else:
61 data = old.extractfile(m)
63 data = old.extractfile(m)
62 new.addfile(m2, data)
64 new.addfile(m2, data)
63 new.close()
65 new.close()
64 old.close()
66 old.close()
65
67
66 buf.seek(0)
68 buf.seek(0)
67
69
68 if r_mode == "r:gz":
70 if r_mode == "r:gz":
69 with open(path, "wb") as f:
71 with open(path, "wb") as f:
70 with gzip.GzipFile("", "wb", fileobj=f, mtime=timestamp) as gzf:
72 with gzip.GzipFile("", "wb", fileobj=f, mtime=timestamp) as gzf:
71 gzf.write(buf.read())
73 gzf.write(buf.read())
72 elif r_mode == "r:xz":
74 elif r_mode == "r:bz2":
73 import lzma
75 import bz2
74
76
75 with lzma.open(path, "wb") as f:
77 with bz2.open(path, "wb") as f:
76 f.write(buf.read())
78 f.write(buf.read())
77
79
78 else:
80 else:
79 assert False
81 assert False
80
82
81 # checks the archive is valid.
83 # checks the archive is valid.
82 archive = tarfile.open(path)
84 archive = tarfile.open(path)
83 names = archive.getnames()
85 names = archive.getnames()
@@ -1,51 +1,51 b''
1 """Various utilities common to IPython release and maintenance tools.
1 """Various utilities common to IPython release and maintenance tools.
2 """
2 """
3
3
4 # Library imports
4 # Library imports
5 import os
5 import os
6 import sys
6 import sys
7
7
8 # Useful shorthands
8 # Useful shorthands
9 pjoin = os.path.join
9 pjoin = os.path.join
10 cd = os.chdir
10 cd = os.chdir
11
11
12 # Constants
12 # Constants
13
13
14 # SSH root address of the archive site
14 # SSH root address of the archive site
15 archive_user = 'ipython@archive.ipython.org'
15 archive_user = 'ipython@archive.ipython.org'
16 archive_dir = 'archive.ipython.org'
16 archive_dir = 'archive.ipython.org'
17 archive = '%s:%s' % (archive_user, archive_dir)
17 archive = '%s:%s' % (archive_user, archive_dir)
18
18
19 # Build commands
19 # Build commands
20 # Source dists
20 # Source dists
21 sdists = "{python} setup.py sdist --formats=xztar,gztar".format(python=sys.executable)
21 sdists = "{python} setup.py sdist --formats=gztar".format(python=sys.executable)
22 # Binary dists
22 # Binary dists
23 def buildwheels():
23 def buildwheels():
24 sh("{python} setup.py bdist_wheel".format(python=sys.executable))
24 sh("{python} setup.py bdist_wheel".format(python=sys.executable))
25
25
26
26
27 # Utility functions
27 # Utility functions
28 def sh(cmd):
28 def sh(cmd):
29 """Run system command in shell, raise SystemExit if it returns an error."""
29 """Run system command in shell, raise SystemExit if it returns an error."""
30 print("$", cmd)
30 print("$", cmd)
31 stat = os.system(cmd)
31 stat = os.system(cmd)
32 #stat = 0 # Uncomment this and comment previous to run in debug mode
32 #stat = 0 # Uncomment this and comment previous to run in debug mode
33 if stat:
33 if stat:
34 raise SystemExit("Command %s failed with code: %s" % (cmd, stat))
34 raise SystemExit("Command %s failed with code: %s" % (cmd, stat))
35
35
36 def get_ipdir():
36 def get_ipdir():
37 """Get IPython directory from command line, or assume it's the one above."""
37 """Get IPython directory from command line, or assume it's the one above."""
38
38
39 # Initialize arguments and check location
39 # Initialize arguments and check location
40 ipdir = pjoin(os.path.dirname(__file__), os.pardir)
40 ipdir = pjoin(os.path.dirname(__file__), os.pardir)
41
41
42 ipdir = os.path.abspath(ipdir)
42 ipdir = os.path.abspath(ipdir)
43
43
44 cd(ipdir)
44 cd(ipdir)
45 if not os.path.isdir('IPython') and os.path.isfile('setup.py'):
45 if not os.path.isdir('IPython') and os.path.isfile('setup.py'):
46 raise SystemExit('Invalid ipython directory: %s' % ipdir)
46 raise SystemExit('Invalid ipython directory: %s' % ipdir)
47 return ipdir
47 return ipdir
48
48
49 def execfile(fname, globs, locs=None):
49 def execfile(fname, globs, locs=None):
50 locs = locs or globs
50 locs = locs or globs
51 exec(compile(open(fname).read(), fname, "exec"), globs, locs)
51 exec(compile(open(fname).read(), fname, "exec"), globs, locs)
General Comments 0
You need to be logged in to leave comments. Login now