Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ python-hosts
[![codecov](https://codecov.io/gh/jonhadfield/python-hosts/branch/devel/graph/badge.svg)](https://codecov.io/gh/jonhadfield/python-hosts) [![Docs](https://img.shields.io/badge/docs-latest-brightgreen.svg?style=flat)](http://python-hosts.readthedocs.org/en/latest/)


This is a python library for managing a hosts file.
This is a python library for managing a hosts file.
It enables you to add and remove entries, or import them from a file or URL.
It remains compatible with Python 2.7 as well as modern Python 3 releases.

Documentation
-------------
Expand Down Expand Up @@ -42,7 +43,7 @@ A command line client using python-hosts can be found here: https://github.com/j
Requirements
------------

Tested on python 2.7, 3.5, 3.6, 3.7, 3.8, 3.9, pypy and pypy3
Tested on Python 2.7 and Python 3.5+, including PyPy variants


License
Expand Down
71 changes: 28 additions & 43 deletions python_hosts/hosts.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,24 +118,16 @@ def str_to_hostentry(entry):
:return: An instance of HostsEntry
"""
split_line = entry.split('#', 1)
inline_comment = None
if len(split_line) == 2:
inline_comment = split_line[1].strip()
line_parts = split_line[0].strip().split()
else:
line_parts = entry.strip().split()
if is_ipv4(line_parts[0]) and valid_hostnames(line_parts[1:]):
return HostsEntry(entry_type='ipv4',
address=line_parts[0],
names=line_parts[1:],
line = split_line[0].strip().split()
inline_comment = split_line[1].strip() if len(split_line) == 2 else None

if is_ipv4(line[0]) and valid_hostnames(line[1:]):
return HostsEntry('ipv4', address=line[0], names=line[1:],
comment=inline_comment)
elif is_ipv6(line_parts[0]) and valid_hostnames(line_parts[1:]):
return HostsEntry(entry_type='ipv6',
address=line_parts[0],
names=line_parts[1:],
if is_ipv6(line[0]) and valid_hostnames(line[1:]):
return HostsEntry('ipv6', address=line[0], names=line[1:],
comment=inline_comment)
else:
return False
return False


class Hosts(object):
Expand Down Expand Up @@ -187,11 +179,16 @@ def determine_hosts_path(platform=None):
"""
if not platform:
platform = sys.platform
if platform.startswith('win'):
result = r"c:\windows\system32\drivers\etc\hosts"
return result
else:
return '/etc/hosts'

paths = {
'win': r"c:\windows\system32\drivers\etc\hosts",
'default': '/etc/hosts'
}

for key, value in paths.items():
if key != 'default' and platform.startswith(key):
return value
return paths['default']

def write(self, path=None, mode='w'):
"""
Expand Down Expand Up @@ -267,12 +264,8 @@ def exists(self, address=None, names=None, comment=None):
if self.find_all_matching(address=address, name=name, comment=comment):
return True

for entry in self.entries:
if entry.entry_type == 'comment' and entry.comment == comment:
return True
# elif entry.entry_type in ('ipv4', 'ipv6'):
# pass # already covered above
return False
return any(entry.entry_type == 'comment' and entry.comment == comment
for entry in self.entries)

def remove_all_matching(self, address=None, name=None, comment=None):
"""
Expand Down Expand Up @@ -304,22 +297,14 @@ def find_all_matching(self, address=None, name=None, comment=None):
:param comment: A host inline comment
:return: HostEntry instances
"""
results = []
if address or name or comment:
for entry in self.entries:
if not entry.is_real_entry():
continue
if address:
if address != entry.address:
continue
if name:
if name not in entry.names:
continue
if comment:
if comment != entry.comment:
continue
results.append(entry)
return results
if not any((address, name, comment)):
return []

return [entry for entry in self.entries
if entry.is_real_entry()
and (address is None or entry.address == address)
and (name is None or name in entry.names)
and (comment is None or entry.comment == comment)]

def import_url(self, url=None, force=None):
"""
Expand Down
16 changes: 15 additions & 1 deletion tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import os
import sys

from python_hosts.utils import is_ipv4, is_ipv6, valid_hostnames
from python_hosts.utils import is_ipv4, is_ipv6, valid_hostnames, dedupe_list, is_readable
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))


Expand Down Expand Up @@ -52,3 +52,17 @@ def test_hostname_validation_failure_with_leading_hyphen():
Test function returns False if a hostname with a leading hyphen is specified
"""
assert not valid_hostnames(['example.com', '-example'])


def test_dedupe_list_preserves_order():
"""Ensure dedupe_list removes duplicates while keeping order."""
items = ['a', 'b', 'a', 'c', 'b']
assert dedupe_list(items) == ['a', 'b', 'c']


def test_is_readable(tmpdir):
"""Check is_readable returns expected values."""
readable = tmpdir.join('file')
readable.write('data')
assert is_readable(readable.strpath)
assert not is_readable(readable.strpath + '_missing')
Loading