Skip to content

Commit

Permalink
Write .dist-info directory for local installs instead of .egg-info
Browse files Browse the repository at this point in the history
Following PEP 376

@dholth, let me know if you see any obvious problems with this. Thanks!
  • Loading branch information
takluyver committed Apr 2, 2015
1 parent 639e622 commit 1f4c9e4
Show file tree
Hide file tree
Showing 4 changed files with 57 additions and 28 deletions.
12 changes: 12 additions & 0 deletions flit/common.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import hashlib
from importlib.machinery import SourceFileLoader
from pathlib import Path

Expand Down Expand Up @@ -62,6 +63,11 @@ def parse_entry_point(ep: str):

return mod, func

def hash_file(path, algorithm='sha256'):
with Path(path).open('rb') as f:
h = hashlib.new(algorithm, f.read())
return h.hexdigest()

class Metadata:

home_page = None
Expand Down Expand Up @@ -136,3 +142,9 @@ def write_metadata_file(self, fp):

if self.description is not None:
fp.write('\n' + self.description + '\n')

def make_metadata(module, ini_info):
md_dict = {'name': module.name, 'provides': [module.name]}
md_dict.update(get_info_from_module(module))
md_dict.update(ini_info['metadata'])
return Metadata(md_dict)
52 changes: 35 additions & 17 deletions flit/install.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"""
import logging
import os
import csv
import pathlib
import shutil
import site
Expand Down Expand Up @@ -50,6 +51,8 @@ def __init__(self, ini_path, user=True, symlink=False):
self.ini_info = inifile.read_pkg_ini(ini_path)
self.module = common.Module(self.ini_info['module'],
ini_path.parent)
self.metadata = common.make_metadata(self.module, self.ini_info)

self.user = user
self.symlink = symlink
self.installed_files = []
Expand Down Expand Up @@ -117,25 +120,40 @@ def install(self):
self.write_dist_info(dirs['purelib'])

def write_dist_info(self, site_pkgs):
# Record metadata about installed files to give pip a fighting chance of
# uninstalling it correctly.
module_info = common.get_info_from_module(self.module)
dist_name = self.ini_info['metadata'].get('name', self.module.name)
egg_info = pathlib.Path(site_pkgs) / '{}-{}.egg-info'.format(
dist_name, module_info['version'])
"""Write dist-info folder, according to PEP 376"""
dist_info = pathlib.Path(site_pkgs) / '{}-{}.dist-info'.format(
self.metadata.name, self.metadata.version)
try:
egg_info.mkdir()
dist_info.mkdir()
except FileExistsError:
shutil.rmtree(str(egg_info))
egg_info.mkdir()
shutil.rmtree(str(dist_info))
dist_info.mkdir()

with (dist_info / 'METADATA').open('w', encoding='utf-8') as f:
self.metadata.write_metadata_file(f)
self.installed_files.append(dist_info / 'METADATA')

with (dist_info / 'INSTALLER').open('w') as f:
f.write('flit')
self.installed_files.append(dist_info / 'INSTALLER')

# Not sure what this is for, but it's easy to do, and perhaps it will
# placate the heathen gods of packaging.
with (egg_info / 'top_level.txt').open('w') as f:
f.write(self.module.name)
self.installed_files.append(egg_info / 'top_level.txt')
# We only handle explicitly requested installations
with (dist_info / 'REQUESTED').open('w'): pass
self.installed_files.append(dist_info / 'REQUESTED')

with (egg_info / 'installed-files.txt').open('w') as f:
with (dist_info / 'RECORD').open('w', encoding='utf-8') as f:
cf = csv.writer(f)
for path in self.installed_files:
rel = os.path.relpath(str(path), str(egg_info))
f.write(rel + '\n')
path = pathlib.Path(path)
if path.is_symlink() or path.suffix in {'.pyc', '.pyo'}:
hash, size = '', ''
else:
hash = 'sha256=' + common.hash_file(path)
size = path.stat().st_size
try:
path = path.relative_to(site_pkgs)
except ValueError:
pass
cf.writerow((path, hash, size))

cf.writerow(((dist_info / 'RECORD').relative_to(site_pkgs), '', ''))
12 changes: 3 additions & 9 deletions flit/wheel.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import configparser
import hashlib
import logging
import os
import shutil
Expand Down Expand Up @@ -29,10 +28,7 @@ def __init__(self, ini_path, upload=None, verify_metadata=None):
self.upload=upload
self.verify_metadata=verify_metadata

md_dict = {'name': self.module.name, 'provides': [self.module.name]}
md_dict.update(common.get_info_from_module(self.module))
md_dict.update(self.ini_info['metadata'])
self.metadata = common.Metadata(md_dict)
self.metadata = common.make_metadata(self.module, self.ini_info)

self.dist_version = self.metadata.name + '-' + self.metadata.version
self.wheel_file = None
Expand Down Expand Up @@ -92,11 +88,9 @@ def write_record(self):
for f in files:
relfile = os.path.join(reldir, f)
file = os.path.join(dirpath, f)
h = hashlib.sha256()
with open(file, 'rb') as fp:
h.update(fp.read())
hash = common.hash_file(file)
size = os.stat(file).st_size
records.append((relfile, h.hexdigest(), size))
records.append((relfile, hash, size))

with (self.dist_info / 'RECORD').open('w') as f:
for path, hash, size in records:
Expand Down
9 changes: 7 additions & 2 deletions tests/test_install.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,21 @@ def tearDown(self):
def test_install_module(self):
Installer(samples_dir / 'module1-pkg.ini').install()
assert_isfile(self.tmpdir / 'site-packages' / 'module1.py')
assert_isdir(self.tmpdir / 'site-packages' / 'module1-0.1.egg-info')
assert_isdir(self.tmpdir / 'site-packages' / 'module1-0.1.dist-info')

def test_install_package(self):
Installer(samples_dir / 'package1-pkg.ini').install()
assert_isdir(self.tmpdir / 'site-packages' / 'package1')
assert_isdir(self.tmpdir / 'site-packages' / 'package1-0.1.egg-info')
assert_isdir(self.tmpdir / 'site-packages' / 'package1-0.1.dist-info')
assert_isfile(self.tmpdir / 'scripts' / 'pkg_script')

def test_symlink_package(self):
Installer(samples_dir / 'package1-pkg.ini', symlink=True).install()
assert_islink(self.tmpdir / 'site-packages' / 'package1',
to=str(samples_dir / 'package1'))
assert_isfile(self.tmpdir / 'scripts' / 'pkg_script')

def test_dist_name(self):
Installer(samples_dir / 'altdistname.ini').install()
assert_isdir(self.tmpdir / 'site-packages' / 'package1')
assert_isdir(self.tmpdir / 'site-packages' / 'packagedist1-0.1.dist-info')

5 comments on commit 1f4c9e4

@dholth
Copy link
Member

@dholth dholth commented on 1f4c9e4 Apr 3, 2015

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks good

@takluyver
Copy link
Member Author

@takluyver takluyver commented on 1f4c9e4 Apr 3, 2015 via email

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dholth
Copy link
Member

@dholth dholth commented on 1f4c9e4 Apr 13, 2015

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it can install directly from the source checkout, make sure the uninstall command does not delete the source.

@takluyver
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had to patch pip to make it not follow symlinks when uninstalling things. If you're using pip >= 6.1, it should be safe.

@dholth
Copy link
Member

@dholth dholth commented on 1f4c9e4 Apr 13, 2015

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I was thinking the solution would be to i.e. only put the .pth file in RECORD. Sounds fine.

Please sign in to comment.