Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
P
pyfs
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
OpenEdx
pyfs
Commits
9ff3da0c
Commit
9ff3da0c
authored
Dec 01, 2012
by
willmcgugan@gmail.com
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Implemented generic validatepath method and optimized normpath
parent
eac17257
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
173 additions
and
104 deletions
+173
-104
fs/base.py
+33
-0
fs/errors.py
+8
-1
fs/osfs/__init__.py
+3
-9
fs/path.py
+41
-43
fs/tempfs.py
+15
-9
fs/tests/__init__.py
+31
-22
fs/tests/test_fs.py
+9
-0
fs/tests/test_path.py
+9
-0
fs/wrapfs/__init__.py
+24
-20
No files found.
fs/base.py
View file @
9ff3da0c
...
...
@@ -252,6 +252,7 @@ class FS(object):
* *free_space* The free space (in bytes) available on the file system
* *total_space* The total space (in bytes) available on the file system
* *virtual* True if the filesystem defers to other filesystems
* *invalid_path_chars* A string containing characters that may not be used in paths
FS implementations may expose non-generic meta data through a self-named namespace. e.g. ``"somefs.some_meta"``
...
...
@@ -282,6 +283,38 @@ class FS(object):
return
False
return
True
def
validatepath
(
self
,
path
):
"""Validate an fs path, throws an :class:`~fs.errors.InvalidPathError` exception if validation fails.
A path is invalid if it fails to map to a path on the underlaying filesystem. The default
implementation checks for the presence of any of the characters in the meta value 'invalid_path_chars',
but implementations may have other requirements for paths.
:param path: an fs path to validatepath
:raises `fs.errors.InvalidPathError`: if `path` does not map on to a valid path on this filesystem
"""
invalid_chars
=
self
.
getmeta
(
'invalid_path_chars'
,
default
=
None
)
if
invalid_chars
:
re_invalid_chars
=
getattr
(
self
,
'_re_invalid_chars'
,
None
)
if
re_invalid_chars
is
None
:
self
.
_re_invalid_chars
=
re_invalid_chars
=
re
.
compile
(
'|'
.
join
(
re
.
escape
(
c
)
for
c
in
invalid_chars
),
re
.
UNICODE
)
if
re_invalid_chars
.
search
(
path
):
raise
InvalidCharsInPathError
(
path
)
def
isvalidpath
(
self
,
path
):
"""Check if a path is valid on this filesystem
:param path: an fs path
"""
try
:
self
.
validatepath
(
path
)
except
InvalidPathError
:
return
False
else
:
return
True
def
getsyspath
(
self
,
path
,
allow_none
=
False
):
"""Returns the system path (a path recognized by the OS) if one is present.
...
...
fs/errors.py
View file @
9ff3da0c
...
...
@@ -11,6 +11,7 @@ catch-all exception.
__all__
=
[
'FSError'
,
'CreateFailedError'
,
'PathError'
,
'InvalidPathError'
,
'InvalidCharsInPathError'
,
'OperationFailedError'
,
'UnsupportedError'
,
...
...
@@ -83,7 +84,13 @@ class PathError(FSError):
super
(
PathError
,
self
)
.
__init__
(
**
kwds
)
class
InvalidCharsInPathError
(
PathError
):
class
InvalidPathError
(
PathError
):
"""Base exception for fs paths that can't be mapped on to the underlaying filesystem."""
default_message
=
"Path is invalid on this filesystem
%(path)
s"
class
InvalidCharsInPathError
(
InvalidPathError
):
"""The path contains characters that are invalid on this filesystem"""
default_message
=
"Path contains invalid characters:
%(path)
s"
...
...
fs/osfs/__init__.py
View file @
9ff3da0c
...
...
@@ -88,10 +88,9 @@ class OSFS(OSFSXAttrMixin, OSFSWatchMixin, FS):
}
if
platform
.
system
()
==
'Windows'
:
_
invalid_path_chars
=
''
.
join
(
chr
(
n
)
for
n
in
xrange
(
31
))
+
'
\\
:*?"<>|'
_
meta
[
"invalid_path_chars"
]
=
''
.
join
(
chr
(
n
)
for
n
in
xrange
(
31
))
+
'
\\
:*?"<>|'
else
:
_invalid_path_chars
=
'
\0
'
_re_invalid_path_chars
=
re
.
compile
(
'|'
.
join
(
re
.
escape
(
c
)
for
c
in
_invalid_path_chars
),
re
.
UNICODE
)
_meta
[
"invalid_path_chars"
]
=
'
\0
'
def
__init__
(
self
,
root_path
,
thread_synchronize
=
_thread_synchronize_default
,
encoding
=
None
,
create
=
False
,
dir_mode
=
0700
,
use_long_paths
=
True
):
"""
...
...
@@ -153,13 +152,8 @@ class OSFS(OSFSXAttrMixin, OSFSWatchMixin, FS):
return
p
return
p
.
decode
(
self
.
encoding
,
'replace'
)
def
_validate_path
(
self
,
path
):
"""Raise an error if there are any invalid characters in the path"""
if
self
.
_re_invalid_path_chars
.
search
(
path
):
raise
InvalidCharsInPathError
(
path
)
def
getsyspath
(
self
,
path
,
allow_none
=
False
):
self
.
_validate_
path
(
path
)
self
.
validate
path
(
path
)
path
=
relpath
(
normpath
(
path
))
.
replace
(
u"/"
,
os
.
sep
)
path
=
os
.
path
.
join
(
self
.
root_path
,
path
)
if
not
path
.
startswith
(
self
.
root_path
):
...
...
fs/path.py
View file @
9ff3da0c
...
...
@@ -14,7 +14,9 @@ import re
import
os
_requires_normalization
=
re
.
compile
(
r'/\.\.|\./|\.|//'
)
.
search
#_requires_normalization = re.compile(r'/\.\.|\./|\.|//').search
# New improved re that avoids normalizing paths that don't need it - WM
_requires_normalization
=
re
.
compile
(
r'/\.\.|\./|^\.$|\.$|//'
)
.
search
def
normpath
(
path
):
...
...
@@ -75,11 +77,6 @@ else:
return
path
def
normospath
(
path
):
"""Normalizes a path with os separators"""
return
normpath
(
ospath
(
path
))
def
iteratepath
(
path
,
numsplits
=
None
):
"""Iterate over the individual components of a path.
...
...
@@ -374,7 +371,7 @@ def isprefix(path1, path2):
bits1
.
pop
()
if
len
(
bits1
)
>
len
(
bits2
):
return
False
for
(
bit1
,
bit2
)
in
zip
(
bits1
,
bits2
):
for
(
bit1
,
bit2
)
in
zip
(
bits1
,
bits2
):
if
bit1
!=
bit2
:
return
False
return
True
...
...
@@ -434,7 +431,7 @@ class PathMap(object):
def
__init__
(
self
):
self
.
_map
=
{}
def
__getitem__
(
self
,
path
):
def
__getitem__
(
self
,
path
):
"""Get the value stored under the given path."""
m
=
self
.
_map
for
name
in
iteratepath
(
path
):
...
...
@@ -447,7 +444,7 @@ class PathMap(object):
except
KeyError
:
raise
KeyError
(
path
)
def
__contains__
(
self
,
path
):
def
__contains__
(
self
,
path
):
"""Check whether the given path has a value stored in the map."""
try
:
self
[
path
]
...
...
@@ -456,22 +453,22 @@ class PathMap(object):
else
:
return
True
def
__setitem__
(
self
,
path
,
value
):
def
__setitem__
(
self
,
path
,
value
):
"""Set the value stored under the given path."""
m
=
self
.
_map
for
name
in
iteratepath
(
path
):
try
:
m
=
m
[
name
]
except
KeyError
:
m
=
m
.
setdefault
(
name
,{})
m
=
m
.
setdefault
(
name
,
{})
m
[
""
]
=
value
def
__delitem__
(
self
,
path
):
def
__delitem__
(
self
,
path
):
"""Delete the value stored under the given path."""
ms
=
[[
self
.
_map
,
None
]]
ms
=
[[
self
.
_map
,
None
]]
for
name
in
iteratepath
(
path
):
try
:
ms
.
append
([
ms
[
-
1
][
0
][
name
],
None
])
ms
.
append
([
ms
[
-
1
][
0
][
name
],
None
])
except
KeyError
:
raise
KeyError
(
path
)
else
:
...
...
@@ -485,19 +482,19 @@ class PathMap(object):
del
ms
[
-
1
]
del
ms
[
-
1
][
0
][
ms
[
-
1
][
1
]]
def
get
(
self
,
path
,
default
=
None
):
def
get
(
self
,
path
,
default
=
None
):
"""Get the value stored under the given path, or the given default."""
try
:
return
self
[
path
]
except
KeyError
:
return
default
def
pop
(
self
,
path
,
default
=
None
):
def
pop
(
self
,
path
,
default
=
None
):
"""Pop the value stored under the given path, or the given default."""
ms
=
[[
self
.
_map
,
None
]]
ms
=
[[
self
.
_map
,
None
]]
for
name
in
iteratepath
(
path
):
try
:
ms
.
append
([
ms
[
-
1
][
0
][
name
],
None
])
ms
.
append
([
ms
[
-
1
][
0
][
name
],
None
])
except
KeyError
:
return
default
else
:
...
...
@@ -512,16 +509,16 @@ class PathMap(object):
del
ms
[
-
1
][
0
][
ms
[
-
1
][
1
]]
return
val
def
setdefault
(
self
,
path
,
value
):
def
setdefault
(
self
,
path
,
value
):
m
=
self
.
_map
for
name
in
iteratepath
(
path
):
try
:
m
=
m
[
name
]
except
KeyError
:
m
=
m
.
setdefault
(
name
,{})
return
m
.
setdefault
(
""
,
value
)
m
=
m
.
setdefault
(
name
,
{})
return
m
.
setdefault
(
""
,
value
)
def
clear
(
self
,
root
=
"/"
):
def
clear
(
self
,
root
=
"/"
):
"""Clear all entries beginning with the given root path."""
m
=
self
.
_map
for
name
in
iteratepath
(
root
):
...
...
@@ -531,7 +528,7 @@ class PathMap(object):
return
m
.
clear
()
def
iterkeys
(
self
,
root
=
"/"
,
m
=
None
):
def
iterkeys
(
self
,
root
=
"/"
,
m
=
None
):
"""Iterate over all keys beginning with the given root path."""
if
m
is
None
:
m
=
self
.
_map
...
...
@@ -540,12 +537,12 @@ class PathMap(object):
m
=
m
[
name
]
except
KeyError
:
return
for
(
nm
,
subm
)
in
m
.
iteritems
():
for
(
nm
,
subm
)
in
m
.
iteritems
():
if
not
nm
:
yield
abspath
(
root
)
else
:
k
=
pathcombine
(
root
,
nm
)
for
subk
in
self
.
iterkeys
(
k
,
subm
):
k
=
pathcombine
(
root
,
nm
)
for
subk
in
self
.
iterkeys
(
k
,
subm
):
yield
subk
def
__iter__
(
self
):
...
...
@@ -554,7 +551,7 @@ class PathMap(object):
def
keys
(
self
,
root
=
"/"
):
return
list
(
self
.
iterkeys
(
root
))
def
itervalues
(
self
,
root
=
"/"
,
m
=
None
):
def
itervalues
(
self
,
root
=
"/"
,
m
=
None
):
"""Iterate over all values whose keys begin with the given root path."""
root
=
normpath
(
root
)
if
m
is
None
:
...
...
@@ -564,18 +561,18 @@ class PathMap(object):
m
=
m
[
name
]
except
KeyError
:
return
for
(
nm
,
subm
)
in
m
.
iteritems
():
for
(
nm
,
subm
)
in
m
.
iteritems
():
if
not
nm
:
yield
subm
else
:
k
=
pathcombine
(
root
,
nm
)
for
subv
in
self
.
itervalues
(
k
,
subm
):
k
=
pathcombine
(
root
,
nm
)
for
subv
in
self
.
itervalues
(
k
,
subm
):
yield
subv
def
values
(
self
,
root
=
"/"
):
def
values
(
self
,
root
=
"/"
):
return
list
(
self
.
itervalues
(
root
))
def
iteritems
(
self
,
root
=
"/"
,
m
=
None
):
def
iteritems
(
self
,
root
=
"/"
,
m
=
None
):
"""Iterate over all (key,value) pairs beginning with the given root."""
root
=
normpath
(
root
)
if
m
is
None
:
...
...
@@ -585,18 +582,18 @@ class PathMap(object):
m
=
m
[
name
]
except
KeyError
:
return
for
(
nm
,
subm
)
in
m
.
iteritems
():
for
(
nm
,
subm
)
in
m
.
iteritems
():
if
not
nm
:
yield
(
abspath
(
normpath
(
root
)),
subm
)
yield
(
abspath
(
normpath
(
root
)),
subm
)
else
:
k
=
pathcombine
(
root
,
nm
)
for
(
subk
,
subv
)
in
self
.
iteritems
(
k
,
subm
):
yield
(
subk
,
subv
)
k
=
pathcombine
(
root
,
nm
)
for
(
subk
,
subv
)
in
self
.
iteritems
(
k
,
subm
):
yield
(
subk
,
subv
)
def
items
(
self
,
root
=
"/"
):
def
items
(
self
,
root
=
"/"
):
return
list
(
self
.
iteritems
(
root
))
def
iternames
(
self
,
root
=
"/"
):
def
iternames
(
self
,
root
=
"/"
):
"""Iterate over all names beneath the given root path.
This is basically the equivalent of listdir() for a PathMap - it yields
...
...
@@ -608,15 +605,17 @@ class PathMap(object):
m
=
m
[
name
]
except
KeyError
:
return
for
(
nm
,
subm
)
in
m
.
iteritems
():
for
(
nm
,
subm
)
in
m
.
iteritems
():
if
nm
and
subm
:
yield
nm
def
names
(
self
,
root
=
"/"
):
def
names
(
self
,
root
=
"/"
):
return
list
(
self
.
iternames
(
root
))
_wild_chars
=
frozenset
(
'*?[]!{}'
)
def
iswildcard
(
path
):
"""Check if a path ends with a wildcard
...
...
@@ -627,8 +626,7 @@ def iswildcard(path):
"""
assert
path
is
not
None
base_chars
=
frozenset
(
basename
(
path
))
return
bool
(
base_chars
.
intersection
(
_wild_chars
))
return
not
_wild_chars
.
isdisjoint
(
path
)
if
__name__
==
"__main__"
:
print
recursepath
(
'a/b/c'
)
fs/tempfs.py
View file @
9ff3da0c
...
...
@@ -10,6 +10,7 @@ import os
import
os.path
import
time
import
tempfile
import
platform
from
fs.osfs
import
OSFS
from
fs.errors
import
*
...
...
@@ -20,7 +21,7 @@ class TempFS(OSFS):
"""Create a Filesystem in a temporary directory (with tempfile.mkdtemp),
and removes it when the TempFS object is cleaned up."""
_meta
=
{
'thread_safe'
:
True
,
'virtual'
:
False
,
'read_only'
:
False
,
...
...
@@ -32,9 +33,14 @@ class TempFS(OSFS):
'atomic.copy'
:
True
,
'atomic.makedir'
:
True
,
'atomic.rename'
:
True
,
'atomic.setcontents'
:
False
'atomic.setcontents'
:
False
}
if
platform
.
system
()
==
'Windows'
:
_meta
[
"invalid_path_chars"
]
=
''
.
join
(
chr
(
n
)
for
n
in
xrange
(
31
))
+
'
\\
:*?"<>|'
else
:
_meta
[
"invalid_path_chars"
]
=
'
\0
'
def
__init__
(
self
,
identifier
=
None
,
temp_dir
=
None
,
dir_mode
=
0700
,
thread_synchronize
=
_thread_synchronize_default
):
"""Creates a temporary Filesystem
...
...
@@ -56,21 +62,21 @@ class TempFS(OSFS):
def
__unicode__
(
self
):
return
u'<TempFS:
%
s>'
%
self
.
_temp_dir
def
__getstate__
(
self
):
# If we are picking a TempFS, we want to preserve its contents,
# so we *don't* do the clean
state
=
super
(
TempFS
,
self
)
.
__getstate__
()
self
.
_cleaned
=
True
return
state
def
__setstate__
(
self
,
state
):
state
=
super
(
TempFS
,
self
)
.
__setstate__
(
state
)
self
.
_cleaned
=
False
#self._temp_dir = tempfile.mkdtemp(self.identifier or "TempFS", dir=self.temp_dir)
def
__setstate__
(
self
,
state
):
state
=
super
(
TempFS
,
self
)
.
__setstate__
(
state
)
self
.
_cleaned
=
False
#self._temp_dir = tempfile.mkdtemp(self.identifier or "TempFS", dir=self.temp_dir)
#super(TempFS, self).__init__(self._temp_dir,
# dir_mode=self.dir_mode,
# thread_synchronize=self.thread_synchronize)
# thread_synchronize=self.thread_synchronize)
def
close
(
self
):
"""Removes the temporary directory.
...
...
fs/tests/__init__.py
View file @
9ff3da0c
...
...
@@ -41,7 +41,7 @@ class FSTestCases(object):
To apply the tests to your own FS implementation, simply use FSTestCase
as a mixin for your own unittest.TestCase subclass and have the setUp
method set self.fs to an instance of your FS implementation.
NB. The Filesystem being tested must have a capacity of at least 3MB.
This class is designed as a mixin so that it's not detected by test
...
...
@@ -52,10 +52,19 @@ class FSTestCases(object):
"""Check that a file exists within self.fs"""
return
self
.
fs
.
exists
(
p
)
def
test_invalid_chars
(
self
):
"""Check paths validate ok"""
# Will have to be overriden selectively for custom validepath methods
self
.
assertEqual
(
self
.
fs
.
validatepath
(
''
),
None
)
self
.
assertEqual
(
self
.
fs
.
validatepath
(
'.foo'
),
None
)
self
.
assertEqual
(
self
.
fs
.
validatepath
(
'foo'
),
None
)
self
.
assertEqual
(
self
.
fs
.
validatepath
(
'foo/bar'
),
None
)
self
.
assert_
(
self
.
fs
.
isvalidpath
(
'foo/bar'
))
def
test_meta
(
self
):
"""Checks getmeta / hasmeta are functioning"""
# getmeta / hasmeta are hard to test, since there is no way to validate
# the implementations response
# the implementation
'
s response
meta_names
=
[
"read_only"
,
"network"
,
"unicode_paths"
]
...
...
@@ -70,7 +79,7 @@ class FSTestCases(object):
self
.
assertTrue
(
self
.
fs
.
hasmeta
(
meta_name
))
except
NoMetaError
:
self
.
assertFalse
(
self
.
fs
.
hasmeta
(
meta_name
))
def
test_root_dir
(
self
):
self
.
assertTrue
(
self
.
fs
.
isdir
(
""
))
...
...
@@ -108,7 +117,7 @@ class FSTestCases(object):
else
:
f
.
close
()
assert
False
,
"ResourceInvalidError was not raised"
def
test_writefile
(
self
):
self
.
assertRaises
(
ResourceNotFoundError
,
self
.
fs
.
open
,
"test1.txt"
)
f
=
self
.
fs
.
open
(
"test1.txt"
,
"wb"
)
...
...
@@ -152,7 +161,7 @@ class FSTestCases(object):
self
.
assertEquals
(
self
.
fs
.
getcontents
(
"hello"
,
"rb"
),
b
(
"world"
))
# ...and a file-like object
self
.
fs
.
setcontents_async
(
"hello"
,
StringIO
(
b
(
"to you, good sir!"
)),
chunk_size
=
2
)
.
wait
()
self
.
assertEquals
(
self
.
fs
.
getcontents
(
"hello"
,
"rb"
),
b
(
"to you, good sir!"
))
self
.
assertEquals
(
self
.
fs
.
getcontents
(
"hello"
,
"rb"
),
b
(
"to you, good sir!"
))
def
test_isdir_isfile
(
self
):
self
.
assertFalse
(
self
.
fs
.
exists
(
"dir1"
))
...
...
@@ -236,7 +245,7 @@ class FSTestCases(object):
for
(
nm
,
info
)
in
items
:
self
.
assertTrue
(
isinstance
(
nm
,
unicode
))
def
check_equal
(
items
,
target
):
names
=
[
nm
for
(
nm
,
info
)
in
items
]
names
=
[
nm
for
(
nm
,
info
)
in
items
]
self
.
assertEqual
(
sorted
(
names
),
sorted
(
target
))
self
.
fs
.
setcontents
(
u"a"
,
b
(
''
))
self
.
fs
.
setcontents
(
"b"
,
b
(
''
))
...
...
@@ -318,7 +327,7 @@ class FSTestCases(object):
if
"c"
in
files
:
found_c
=
True
if
"a.txt"
in
files
:
break
break
assert
found_c
,
"depth search order was wrong: "
+
str
(
list
(
self
.
fs
.
walk
(
search
=
"depth"
)))
def
test_walk_wildcard
(
self
):
...
...
@@ -730,18 +739,18 @@ class FSTestCases(object):
f
.
truncate
()
checkcontents
(
"hello"
,
b
(
"12345"
))
def
test_truncate_to_larger_size
(
self
):
with
self
.
fs
.
open
(
"hello"
,
"wb"
)
as
f
:
def
test_truncate_to_larger_size
(
self
):
with
self
.
fs
.
open
(
"hello"
,
"wb"
)
as
f
:
f
.
truncate
(
30
)
self
.
assertEquals
(
self
.
fs
.
getsize
(
"hello"
),
30
)
# Some file systems (FTPFS) don't support both reading and writing
if
self
.
fs
.
getmeta
(
'file.read_and_write'
,
True
):
with
self
.
fs
.
open
(
"hello"
,
"rb+"
)
as
f
:
f
.
seek
(
25
)
f
.
write
(
b
(
"123456"
))
with
self
.
fs
.
open
(
"hello"
,
"rb"
)
as
f
:
f
.
seek
(
25
)
self
.
assertEquals
(
f
.
read
(),
b
(
"123456"
))
...
...
@@ -788,10 +797,10 @@ class FSTestCases(object):
else
:
# Just make sure it doesn't throw an exception
fs2
=
pickle
.
loads
(
pickle
.
dumps
(
self
.
fs
))
def
test_big_file
(
self
):
"""Test handling of a big file (1MB)"""
"""Test handling of a big file (1MB)"""
chunk_size
=
1024
*
256
num_chunks
=
4
def
chunk_stream
():
...
...
@@ -821,19 +830,19 @@ class FSTestCases(object):
finally
:
f
.
close
()
def
test_settimes
(
self
):
def
test_settimes
(
self
):
def
cmp_datetimes
(
d1
,
d2
):
"""Test datetime objects are the same to within the timestamp accuracy"""
dts1
=
time
.
mktime
(
d1
.
timetuple
())
dts2
=
time
.
mktime
(
d2
.
timetuple
())
return
int
(
dts1
)
==
int
(
dts2
)
return
int
(
dts1
)
==
int
(
dts2
)
d1
=
datetime
.
datetime
(
2010
,
6
,
20
,
11
,
0
,
9
,
987699
)
d2
=
datetime
.
datetime
(
2010
,
7
,
5
,
11
,
0
,
9
,
500000
)
self
.
fs
.
setcontents
(
'/dates.txt'
,
b
(
'check dates'
))
d2
=
datetime
.
datetime
(
2010
,
7
,
5
,
11
,
0
,
9
,
500000
)
self
.
fs
.
setcontents
(
'/dates.txt'
,
b
(
'check dates'
))
# If the implementation supports settimes, check that the times
# can be set and then retrieved
try
:
self
.
fs
.
settimes
(
'/dates.txt'
,
d1
,
d2
)
self
.
fs
.
settimes
(
'/dates.txt'
,
d1
,
d2
)
except
UnsupportedError
:
pass
else
:
...
...
@@ -847,7 +856,7 @@ class FSTestCases(object):
# May be disabled - see end of file
class
ThreadingTestCases
(
object
):
"""Testcases for thread-safety of FS implementations."""
# These are either too slow to be worth repeating,
# or cannot possibly break cross-thread.
_dont_retest
=
(
"test_pickling"
,
"test_multiple_overwrite"
,)
...
...
@@ -1026,7 +1035,7 @@ class ThreadingTestCases(object):
self
.
fs
.
copydir
(
"a"
,
"copy of a"
)
def
copydir_overwrite
():
self
.
_yield
()
self
.
fs
.
copydir
(
"a"
,
"copy of a"
,
overwrite
=
True
)
self
.
fs
.
copydir
(
"a"
,
"copy of a"
,
overwrite
=
True
)
# This should error out since we're not overwriting
self
.
assertRaises
(
DestinationExistsError
,
self
.
_runThreads
,
copydir
,
copydir
)
# This should run to completion and give a valid state, unless
...
...
@@ -1059,4 +1068,4 @@ class ThreadingTestCases(object):
# Uncomment to temporarily disable threading tests
#class ThreadingTestCases(object):
# _dont_retest = ()
# _dont_retest = ()
fs/tests/test_fs.py
View file @
9ff3da0c
...
...
@@ -31,7 +31,16 @@ class TestOSFS(unittest.TestCase,FSTestCases,ThreadingTestCases):
return
os
.
path
.
exists
(
os
.
path
.
join
(
self
.
temp_dir
,
relpath
(
p
)))
def
test_invalid_chars
(
self
):
self
.
assertEqual
(
self
.
fs
.
validatepath
(
''
),
None
)
self
.
assertEqual
(
self
.
fs
.
validatepath
(
'.foo'
),
None
)
self
.
assertEqual
(
self
.
fs
.
validatepath
(
'foo'
),
None
)
self
.
assertEqual
(
self
.
fs
.
validatepath
(
'foo/bar'
),
None
)
self
.
assert_
(
self
.
fs
.
isvalidpath
(
'foo/bar'
))
self
.
assertRaises
(
errors
.
InvalidCharsInPathError
,
self
.
fs
.
open
,
'invalid
\0
file'
,
'wb'
)
self
.
assertFalse
(
self
.
fs
.
isvalidpath
(
'invalid
\0
file'
))
self
.
assert_
(
self
.
fs
.
isvalidpath
(
'validfile'
))
self
.
assert_
(
self
.
fs
.
isvalidpath
(
'completely_valid/path/foo.bar'
))
class
TestSubFS
(
unittest
.
TestCase
,
FSTestCases
,
ThreadingTestCases
):
...
...
fs/tests/test_path.py
View file @
9ff3da0c
...
...
@@ -138,6 +138,15 @@ class TestPathFunctions(unittest.TestCase):
for
path
,
test_basename
in
tests
:
self
.
assertEqual
(
basename
(
path
),
test_basename
)
def
test_iswildcard
(
self
):
self
.
assert_
(
iswildcard
(
'*'
))
self
.
assert_
(
iswildcard
(
'*.jpg'
))
self
.
assert_
(
iswildcard
(
'foo/*'
))
self
.
assert_
(
iswildcard
(
'foo/{}'
))
self
.
assertFalse
(
iswildcard
(
'foo'
))
self
.
assertFalse
(
iswildcard
(
'img.jpg'
))
self
.
assertFalse
(
iswildcard
(
'foo/bar'
))
class
Test_PathMap
(
unittest
.
TestCase
):
...
...
fs/wrapfs/__init__.py
View file @
9ff3da0c
...
...
@@ -49,7 +49,7 @@ class WrapFS(FS):
and/or contents of files in an FS. It could be used to implement
e.g. compression or encryption in a relatively painless manner.
The following methods can be overridden to control how files are
The following methods can be overridden to control how files are
accessed in the underlying FS object:
* _file_wrap(file, mode): called for each file that is opened from
...
...
@@ -66,10 +66,10 @@ class WrapFS(FS):
"""
def
__init__
(
self
,
fs
):
super
(
WrapFS
,
self
)
.
__init__
()
super
(
WrapFS
,
self
)
.
__init__
()
try
:
self
.
_lock
=
fs
.
_lock
except
(
AttributeError
,
FSError
):
except
(
AttributeError
,
FSError
):
self
.
_lock
=
self
.
_lock
=
threading
.
RLock
()
self
.
wrapped_fs
=
fs
...
...
@@ -116,7 +116,7 @@ class WrapFS(FS):
transparent file compression - in this case files from the wrapped
FS cannot be opened in append mode.
"""
return
(
mode
,
mode
)
return
(
mode
,
mode
)
def
__unicode__
(
self
):
return
u"<
%
s:
%
s>"
%
(
self
.
__class__
.
__name__
,
self
.
wrapped_fs
,)
...
...
@@ -128,18 +128,22 @@ class WrapFS(FS):
@rewrite_errors
def
getmeta
(
self
,
meta_name
,
default
=
NoDefaultMeta
):
return
self
.
wrapped_fs
.
getmeta
(
meta_name
,
default
)
@rewrite_errors
def
hasmeta
(
self
,
meta_name
):
return
self
.
wrapped_fs
.
hasmeta
(
meta_name
)
@rewrite_errors
def
validatepath
(
self
,
path
):
return
self
.
wrapped_fs
.
validatepath
(
self
.
_encode
(
path
))
@rewrite_errors
def
getsyspath
(
self
,
path
,
allow_none
=
False
):
return
self
.
wrapped_fs
.
getsyspath
(
self
.
_encode
(
path
),
allow_none
)
return
self
.
wrapped_fs
.
getsyspath
(
self
.
_encode
(
path
),
allow_none
)
@rewrite_errors
def
getpathurl
(
self
,
path
,
allow_none
=
False
):
return
self
.
wrapped_fs
.
getpathurl
(
self
.
_encode
(
path
),
allow_none
)
return
self
.
wrapped_fs
.
getpathurl
(
self
.
_encode
(
path
),
allow_none
)
@rewrite_errors
def
hassyspath
(
self
,
path
):
...
...
@@ -154,9 +158,9 @@ class WrapFS(FS):
@rewrite_errors
def
setcontents
(
self
,
path
,
data
,
chunk_size
=
64
*
1024
):
# We can't pass setcontents() through to the wrapped FS if the
# wrapper has defined a _file_wrap method, as it would bypass
# wrapper has defined a _file_wrap method, as it would bypass
# the file contents wrapping.
#if self._file_wrap.im_func is WrapFS._file_wrap.im_func:
#if self._file_wrap.im_func is WrapFS._file_wrap.im_func:
if
getattr
(
self
.
__class__
,
'_file_wrap'
,
None
)
is
getattr
(
WrapFS
,
'_file_wrap'
,
None
):
return
self
.
wrapped_fs
.
setcontents
(
self
.
_encode
(
path
),
data
,
chunk_size
=
chunk_size
)
else
:
...
...
@@ -184,7 +188,7 @@ class WrapFS(FS):
full
=
full
,
absolute
=
absolute
,
dirs_only
=
dirs_only
,
files_only
=
files_only
)
files_only
=
files_only
)
full
=
kwds
.
pop
(
"full"
,
False
)
absolute
=
kwds
.
pop
(
"absolute"
,
False
)
wildcard
=
kwds
.
pop
(
"wildcard"
,
None
)
...
...
@@ -192,7 +196,7 @@ class WrapFS(FS):
wildcard
=
lambda
fn
:
True
elif
not
callable
(
wildcard
):
wildcard_re
=
re
.
compile
(
fnmatch
.
translate
(
wildcard
))
wildcard
=
lambda
fn
:
bool
(
wildcard_re
.
match
(
fn
))
wildcard
=
lambda
fn
:
bool
(
wildcard_re
.
match
(
fn
))
entries
=
[]
enc_path
=
self
.
_encode
(
path
)
for
e
in
self
.
wrapped_fs
.
listdir
(
enc_path
,
**
kwds
):
...
...
@@ -203,7 +207,7 @@ class WrapFS(FS):
e
=
pathcombine
(
path
,
e
)
elif
absolute
:
e
=
abspath
(
pathcombine
(
path
,
e
))
entries
.
append
(
e
)
entries
.
append
(
e
)
return
entries
@rewrite_errors
...
...
@@ -212,7 +216,7 @@ class WrapFS(FS):
full
=
full
,
absolute
=
absolute
,
dirs_only
=
dirs_only
,
files_only
=
files_only
)
files_only
=
files_only
)
full
=
kwds
.
pop
(
"full"
,
False
)
absolute
=
kwds
.
pop
(
"absolute"
,
False
)
wildcard
=
kwds
.
pop
(
"wildcard"
,
None
)
...
...
@@ -220,7 +224,7 @@ class WrapFS(FS):
wildcard
=
lambda
fn
:
True
elif
not
callable
(
wildcard
):
wildcard_re
=
re
.
compile
(
fnmatch
.
translate
(
wildcard
))
wildcard
=
lambda
fn
:
bool
(
wildcard_re
.
match
(
fn
))
wildcard
=
lambda
fn
:
bool
(
wildcard_re
.
match
(
fn
))
enc_path
=
self
.
_encode
(
path
)
for
e
in
self
.
wrapped_fs
.
ilistdir
(
enc_path
,
**
kwds
):
e
=
basename
(
self
.
_decode
(
pathcombine
(
enc_path
,
e
)))
...
...
@@ -238,7 +242,7 @@ class WrapFS(FS):
full
=
full
,
absolute
=
absolute
,
dirs_only
=
dirs_only
,
files_only
=
files_only
)
files_only
=
files_only
)
full
=
kwds
.
pop
(
"full"
,
False
)
absolute
=
kwds
.
pop
(
"absolute"
,
False
)
wildcard
=
kwds
.
pop
(
"wildcard"
,
None
)
...
...
@@ -246,7 +250,7 @@ class WrapFS(FS):
wildcard
=
lambda
fn
:
True
elif
not
callable
(
wildcard
):
wildcard_re
=
re
.
compile
(
fnmatch
.
translate
(
wildcard
))
wildcard
=
lambda
fn
:
bool
(
wildcard_re
.
match
(
fn
))
wildcard
=
lambda
fn
:
bool
(
wildcard_re
.
match
(
fn
))
entries
=
[]
enc_path
=
self
.
_encode
(
path
)
for
(
nm
,
info
)
in
self
.
wrapped_fs
.
listdirinfo
(
enc_path
,
**
kwds
):
...
...
@@ -274,7 +278,7 @@ class WrapFS(FS):
wildcard
=
lambda
fn
:
True
elif
not
callable
(
wildcard
):
wildcard_re
=
re
.
compile
(
fnmatch
.
translate
(
wildcard
))
wildcard
=
lambda
fn
:
bool
(
wildcard_re
.
match
(
fn
))
wildcard
=
lambda
fn
:
bool
(
wildcard_re
.
match
(
fn
))
enc_path
=
self
.
_encode
(
path
)
for
(
nm
,
info
)
in
self
.
wrapped_fs
.
ilistdirinfo
(
enc_path
,
**
kwds
):
nm
=
basename
(
self
.
_decode
(
pathcombine
(
enc_path
,
nm
)))
...
...
@@ -299,7 +303,7 @@ class WrapFS(FS):
else
:
if
wildcard
is
not
None
and
not
callable
(
wildcard
):
wildcard_re
=
re
.
compile
(
fnmatch
.
translate
(
wildcard
))
wildcard
=
lambda
fn
:
bool
(
wildcard_re
.
match
(
fn
))
wildcard
=
lambda
fn
:
bool
(
wildcard_re
.
match
(
fn
))
for
(
dirpath
,
filepaths
)
in
self
.
wrapped_fs
.
walk
(
self
.
_encode
(
path
),
search
=
search
,
ignore_errors
=
ignore_errors
):
filepaths
=
[
basename
(
self
.
_decode
(
pathcombine
(
dirpath
,
p
)))
for
p
in
filepaths
]
...
...
@@ -321,7 +325,7 @@ class WrapFS(FS):
else
:
if
wildcard
is
not
None
and
not
callable
(
wildcard
):
wildcard_re
=
re
.
compile
(
fnmatch
.
translate
(
wildcard
))
wildcard
=
lambda
fn
:
bool
(
wildcard_re
.
match
(
fn
))
wildcard
=
lambda
fn
:
bool
(
wildcard_re
.
match
(
fn
))
for
filepath
in
self
.
wrapped_fs
.
walkfiles
(
self
.
_encode
(
path
),
search
=
search
,
ignore_errors
=
ignore_errors
):
filepath
=
abspath
(
self
.
_decode
(
filepath
))
if
wildcard
is
not
None
:
...
...
@@ -469,7 +473,7 @@ def wrap_fs_methods(decorator, cls=None, exclude=[]):
wrap_fs_methods
.
method_names
=
[
"open"
,
"exists"
,
"isdir"
,
"isfile"
,
"listdir"
,
"makedir"
,
"remove"
,
"setcontents"
,
"removedir"
,
"rename"
,
"getinfo"
,
"copy"
,
"move"
,
"copydir"
,
"movedir"
,
"close"
,
"getxattr"
,
"setxattr"
,
"delxattr"
,
"listxattrs"
,
"getsyspath"
,
"createfile"
,
"hasmeta"
,
"getmeta"
,
"listdirinfo"
,
"listxattrs"
,
"
validatepath"
,
"
getsyspath"
,
"createfile"
,
"hasmeta"
,
"getmeta"
,
"listdirinfo"
,
"ilistdir"
,
"ilistdirinfo"
]
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment