Skip to content

Commit a377df0

Browse files
authored
added hash method (#259)
* added hash method * add deprecation notice to docstring * docstring * docs * fix hash test * check default is md5 * made hash name a positional arg * test new name decorator * version bump
1 parent 710daf2 commit a377df0

File tree

9 files changed

+66
-3
lines changed

9 files changed

+66
-3
lines changed

CHANGELOG.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,16 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](http://keepachangelog.com/)
66
and this project adheres to [Semantic Versioning](http://semver.org/).
77

8-
## [2.2.2] - Unreleased
8+
## [2.3.0] - 2018-01-30
99

1010
### Fixed
1111

1212
- IllegalBackReference had mangled error message
1313

14+
### Added
15+
16+
- FS.hash method
17+
1418
## [2.2.1] - 2018-01-06
1519

1620
### Fixed

docs/source/implementers.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,8 +130,9 @@ These methods SHOULD NOT be implemented.
130130

131131
Implementing these is highly unlikely to be worthwhile.
132132

133+
* :meth:`~fs.base.FS.check`
133134
* :meth:`~fs.base.FS.getbasic`
134135
* :meth:`~fs.base.FS.getdetails`
135-
* :meth:`~fs.base.FS.check`
136+
* :meth:`~fs.base.FS.hash`
136137
* :meth:`~fs.base.FS.match`
137138
* :meth:`~fs.base.FS.tree`

docs/source/interface.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ The following is a complete list of methods on PyFilesystem objects.
2525
* :meth:`~fs.base.FS.gettype` Get the type of a resource.
2626
* :meth:`~fs.base.FS.geturl` Get a URL to a resource, if one exists.
2727
* :meth:`~fs.base.FS.hassyspath` Check if a resource maps to the OS filesystem.
28+
* :meth:`~fs.base.FS.hash` Get the hash of a file's contents.
2829
* :meth:`~fs.base.FS.hasurl` Check if a resource has a URL.
2930
* :meth:`~fs.base.FS.isclosed` Check if the filesystem is closed.
3031
* :meth:`~fs.base.FS.isempty` Check if a directory is empty.

fs/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
"""Version, used in module and setup.py.
22
"""
3-
__version__ = "2.2.2a0"
3+
__version__ = "2.3.0"

fs/base.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from __future__ import absolute_import, print_function, unicode_literals
1010

1111
import abc
12+
import hashlib
1213
import itertools
1314
import os
1415
import threading
@@ -65,6 +66,7 @@
6566
def _new_name(method, old_name):
6667
"""Return a method with a deprecation warning."""
6768
# Looks suspiciously like a decorator, but isn't!
69+
6870
@wraps(method)
6971
def _method(*args, **kwargs):
7072
warnings.warn(
@@ -75,6 +77,14 @@ def _method(*args, **kwargs):
7577
)
7678
return method(*args, **kwargs)
7779

80+
_method.__doc__ += """
81+
Note:
82+
.. deprecated:: 2.2.0
83+
Please use `~{}`
84+
""".format(
85+
method.__name__
86+
)
87+
7888
return _method
7989

8090

@@ -1593,3 +1603,31 @@ def tree(self, **kwargs):
15931603
from .tree import render
15941604

15951605
render(self, **kwargs)
1606+
1607+
def hash(self, path, name):
1608+
# type: (Text, Text) -> Text
1609+
"""Get the hash of a file's contents.
1610+
1611+
Arguments:
1612+
path(str): A path on the filesystem.
1613+
name(str): One of the algorithms supported by the hashlib module, e.g. `"md5"`
1614+
1615+
Returns:
1616+
str: The hex digest of the hash.
1617+
1618+
Raises:
1619+
fs.errors.UnsupportedHash: If the requested hash is not supported.
1620+
1621+
"""
1622+
_path = self.validatepath(path)
1623+
try:
1624+
hash_object = hashlib.new(name)
1625+
except ValueError:
1626+
raise errors.UnsupportedHash("hash '{}' is not supported".format(name))
1627+
with self.openbin(path) as binary_file:
1628+
while True:
1629+
chunk = binary_file.read(1024 * 1024)
1630+
if not chunk:
1631+
break
1632+
hash_object.update(chunk)
1633+
return hash_object.hexdigest()

fs/errors.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,3 +364,12 @@ def __init__(self, path):
364364

365365
def __reduce__(self):
366366
return type(self), (self.path,)
367+
368+
369+
class UnsupportedHash(ValueError):
370+
"""The requested hash algorithm is not supported.
371+
372+
This exception will be thrown if a hash algorithm is requested that is
373+
not supported by hashlib.
374+
375+
"""

fs/info.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ class Info(object):
4545
4646
"""
4747

48+
__slots__ = ["raw", "_to_datetime", "namespaces"]
49+
4850
def __init__(self, raw_info, to_datetime=epoch_to_datetime):
4951
# type: (RawInfo, ToDatetime) -> None
5052
"""Create a resource info object from a raw info dict.

fs/test.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1834,3 +1834,10 @@ def test_case_sensitive(self):
18341834
def test_glob(self):
18351835
self.assertIsInstance(self.fs.glob, glob.BoundGlobber)
18361836

1837+
def test_hash(self):
1838+
self.fs.writebytes("hashme.txt", b"foobar" * 1024)
1839+
self.assertEqual(
1840+
self.fs.hash("hashme.txt", "md5"), "9fff4bb103ab8ce4619064109c54cb9c"
1841+
)
1842+
with self.assertRaises(errors.UnsupportedHash):
1843+
self.fs.hash("hashme.txt", "nohash")

tests/test_new_name.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
class TestNewNameDecorator(unittest.TestCase):
1111
def double(self, n):
12+
"Double a number"
1213
return n * 2
1314

1415
times_2 = _new_name(double, "times_2")

0 commit comments

Comments
 (0)