zope.file-0.6.2/ 0000775 0001750 0001750 00000000000 11763154606 013031 5 ustar uli uli 0000000 0000000 zope.file-0.6.2/setup.cfg 0000664 0001750 0001750 00000000073 11763154606 014652 0 ustar uli uli 0000000 0000000 [egg_info]
tag_build =
tag_date = 0
tag_svn_revision = 0
zope.file-0.6.2/src/ 0000775 0001750 0001750 00000000000 11763154606 013620 5 ustar uli uli 0000000 0000000 zope.file-0.6.2/src/zope/ 0000775 0001750 0001750 00000000000 11763154606 014575 5 ustar uli uli 0000000 0000000 zope.file-0.6.2/src/zope/file/ 0000775 0001750 0001750 00000000000 11763154606 015514 5 ustar uli uli 0000000 0000000 zope.file-0.6.2/src/zope/file/upload.py 0000664 0001750 0001750 00000012556 11763153771 017365 0 ustar uli uli 0000000 0000000 ##############################################################################
#
# Copyright (c) 2005 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Upload view for zope.file objects.
"""
__docformat__ = "reStructuredText"
import re
import zope.container.interfaces
import zope.contenttype.parse
import zope.lifecycleevent
import zope.component
import zope.event
import zope.file.file
import zope.formlib.form
import zope.mimetype.event
import zope.mimetype.interfaces
import zope.schema
import zope.security.proxy
from zope import mimetype
from zope.file.i18n import _
_nameFinder = re.compile(r'(.*[\\/:])?(.+)')
def nameFinder(fileob):
name = getattr(fileob, 'filename', None)
if name is None:
return None
match = _nameFinder.match(name)
if match is not None:
match = match.group(2)
return match
class Upload(zope.formlib.form.AddForm):
form_fields = zope.formlib.form.fields(
zope.schema.Bytes(
__name__="data",
title=_("Upload data"),
description=_("Upload file"),
),
)
def create(self, data):
ob = self._create_instance(data)
f = self.request.form["form.data"]
updateBlob(ob, f)
self._name = nameFinder(f)
return ob
def add(self, ob):
if self._name and self.context.nameAllowed():
nc = zope.container.interfaces.INameChooser(
self.context.context)
name = nc.chooseName(self._name, ob)
self.context.contentName = name
res = super(Upload, self).add(ob)
# We need to slam on an interface based on the effective MIME type;
# start by getting the IContentInfo for ob:
ci = mimetype.interfaces.IContentInfo(ob, None)
if (ci is not None) and ci.effectiveMimeType:
# we might have *something*
iface = zope.component.queryUtility(
mimetype.interfaces.IContentTypeInterface,
ci.effectiveMimeType)
if iface is not None:
mimetype.event.changeContentType(ob, iface)
return res
def _create_instance(self, data):
return zope.file.file.File()
class Reupload(zope.formlib.form.Form):
form_fields = zope.formlib.form.Fields(
zope.schema.Bytes(
__name__="data",
title=_("Upload data"),
description=_("Upload file to replace the current data"),
),
)
@zope.formlib.form.action(_("Edit"))
def upload(self, action, data):
context = self.context
if "charset" in context.parameters:
old_codec = zope.component.queryUtility(
mimetype.interfaces.ICharsetCodec,
context.parameters["charset"])
else:
old_codec = None
if data.get("data") is not None:
updateBlob(context, self.request.form["form.data"])
# update the encoding, but only if the new content type is encoded
encoded = mimetype.interfaces.IContentTypeEncoded.providedBy(
context)
if encoded and "charset" in context.parameters:
codec = zope.component.queryUtility(
mimetype.interfaces.ICharsetCodec,
context.parameters["charset"])
if codec and getattr(old_codec, "name", None) != codec.name:
# use the preferred charset for the new codec
new_charset = zope.component.getUtility(
mimetype.interfaces.ICodecPreferredCharset,
codec.name)
parameters = dict(context.parameters)
parameters["charset"] = new_charset.name
context.parameters = parameters
# these subscribers generally expect an unproxied object.
zope.event.notify(
zope.lifecycleevent.ObjectModifiedEvent(
zope.security.proxy.removeSecurityProxy(context)))
def updateBlob(ob, input):
# Bypass the widget machinery for now; we'd rather have a blob
# widget that exposes the interesting information from the
# upload.
f = input
# We need to seek back since the form machinery has already
# read all the data :-(
f.seek(0)
data = f.read()
contentType = f.headers.get("Content-Type")
mimeTypeGetter = zope.component.getUtility(
mimetype.interfaces.IMimeTypeGetter)
mimeType = mimeTypeGetter(data=data, content_type=contentType,
name=nameFinder(f))
if not mimeType:
mimeType = "application/octet-stream"
if contentType:
major, minor, parameters = zope.contenttype.parse.parse(
contentType)
if "charset" in parameters:
parameters["charset"] = parameters["charset"].lower()
ob.mimeType = mimeType
ob.parameters = parameters
else:
ob.mimeType = mimeType
ob.parameters = {}
w = ob.open("w")
w.write(data)
w.close()
zope.file-0.6.2/src/zope/file/browser.py 0000664 0001750 0001750 00000002275 11763153771 017561 0 ustar uli uli 0000000 0000000 ##############################################################################
#
# Copyright (c) 2005 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Presentation adapters for zope.file.
"""
__docformat__ = "reStructuredText"
import zope.size
import zope.size.interfaces
import zope.component
import zope.file.interfaces
import zope.interface
class Sized(object):
zope.interface.implements(
zope.size.interfaces.ISized)
zope.component.adapts(
zope.file.interfaces.IFile)
def __init__(self, context):
self.context = context
def sizeForSorting(self):
return "byte", self.context.size
def sizeForDisplay(self):
return zope.size.byteDisplay(self.context.size)
zope.file-0.6.2/src/zope/file/menus.zcml 0000664 0001750 0001750 00000001555 11763153771 017542 0 ustar uli uli 0000000 0000000
zope.file-0.6.2/src/zope/file/README.txt 0000664 0001750 0001750 00000010210 11763153771 017206 0 ustar uli uli 0000000 0000000 ===========
File Object
===========
The `zope.file` package provides a content object used to store a
file. The interface supports efficient upload and download. Let's
create an instance:
>>> from zope.file.file import File
>>> f = File()
The object provides a limited number of data attributes. The
`mimeType` attribute is used to store the preferred MIME
content-type value for the data:
>>> f.mimeType
>>> f.mimeType = "text/plain"
>>> f.mimeType
'text/plain'
>>> f.mimeType = "application/postscript"
>>> f.mimeType
'application/postscript'
The `parameters` attribute is a mapping used to store the content-type
parameters. This is where encoding information can be found when
applicable (and available):
>>> f.parameters
{}
>>> f.parameters["charset"] = "us-ascii"
>>> f.parameters["charset"]
'us-ascii'
Both, `parameters` and `mimeType` can optionally also be set when
creating a `File` object:
>>> f2 = File(mimeType = "application/octet-stream",
... parameters = dict(charset = "utf-8"))
>>> f2.mimeType
'application/octet-stream'
>>> f2.parameters["charset"]
'utf-8'
File objects also sport a `size` attribute that provides the number of
bytes in the file:
>>> f.size
0
The object supports efficient upload and download by providing all
access to content data through accessor objects that provide (subsets
of) Python's file API.
A file that hasn't been written to is empty. We can get a reader by calling
`open()`. Note that all blobs are binary, thus the mode always contains a
'b':
>>> r = f.open("r")
>>> r.mode
'rb'
The `read()` method can be called with a non-negative integer argument
to specify how many bytes to read, or with a negative or omitted
argument to read to the end of the file:
>>> r.read(10)
''
>>> r.read()
''
>>> r.read(-1)
''
Once the accessor has been closed, we can no longer read from it:
>>> r.close()
>>> r.read()
Traceback (most recent call last):
ValueError: I/O operation on closed file
We'll see that readers are more interesting once there's data in the
file object.
Data is added by using a writer, which is also created using the
`open()` method on the file, but requesting a write file mode:
>>> w = f.open("w")
>>> w.mode
'wb'
The `write()` method is used to add data to the file, but note that
the data may be buffered in the writer:
>>> w.write("some text ")
>>> w.write("more text")
The `flush()` method ensure that the data written so far is written to
the file object:
>>> w.flush()
We need to close the file first before determining its file size
>>> w.close()
>>> f.size
19
We can now use a reader to see that the data has been written to the
file:
>>> w = f.open("w")
>>> w.write('some text more text')
>>> w.write(" still more")
>>> w.close()
>>> f.size
30
Now create a new reader and let's perform some seek operations.
>>> r = f.open()
The reader also has a `seek()` method that can be used to back up or
skip forward in the data stream. Simply passing an offset argument,
we see that the current position is moved to that offset from the
start of the file:
>>> r.seek(20)
>>> r.read()
'still more'
That's equivalent to passing 0 as the `whence` argument:
>>> r.seek(20, 0)
>>> r.read()
'still more'
We can skip backward and forward relative to the current position by
passing 1 for `whence`:
>>> r.seek(-10, 1)
>>> r.read(5)
'still'
>>> r.seek(2, 1)
>>> r.read()
'ore'
We can skip to some position backward from the end of the file using
the value 2 for `whence`:
>>> r.seek(-10, 2)
>>> r.read()
'still more'
>>> r.seek(0)
>>> r.seek(-4, 2)
>>> r.read()
'more'
>>> r.close()
Attempting to write to a closed writer raises an exception:
>>> w = f.open('w')
>>> w.close()
>>> w.write('foobar')
Traceback (most recent call last):
ValueError: I/O operation on closed file
Similarly, using `seek()` or `tell()` on a closed reader raises an
exception:
>>> r.close()
>>> r.seek(0)
Traceback (most recent call last):
ValueError: I/O operation on closed file
>>> r.tell()
Traceback (most recent call last):
ValueError: I/O operation on closed file
zope.file-0.6.2/src/zope/file/upload.txt 0000664 0001750 0001750 00000012157 11763153771 017551 0 ustar uli uli 0000000 0000000 ====================
Uploading a new file
====================
There's a simple view for uploading a new file. Let's try it:
>>> from StringIO import StringIO
>>> sio = StringIO("some text")
>>> sio.filename = "plain.txt"
>>> sio.headers = {"Content-Type": "text/plain; charset=utf-8",
... "Content-Disposition": 'attachment; filename="plain.txt"'}
>>> print http("""
... POST /@@+/zope.file.File HTTP/1.1
... Authorization: Basic mgr:mgrpw
... """, form={"form.data": sio,
... "form.actions.add": "Add"}, handle_errors=False)
HTTP/1.1 303 ...
Now, let's request the download view of the file object and check the
result:
>>> print http("""
... GET /plain.txt/@@download HTTP/1.1
... Authorization: Basic mgr:mgrpw
... """, handle_errors=False)
HTTP/1.1 200 Ok
Content-Disposition: attachment; filename="plain.txt"
Content-Length: 9
Content-Type: text/plain;charset=utf-8
some text
We'll peek into the database to make sure the object implements the
expected MIME type interface:
>>> from zope.mimetype import types
>>> ob = getRootFolder()["plain.txt"]
>>> types.IContentTypeTextPlain.providedBy(ob)
True
We can upload new data into our file object as well:
>>> sio = StringIO("new text")
>>> sio.filename = "stuff.txt"
>>> sio.headers = {"Content-Type": "text/plain; charset=utf-8",
... "Content-Disposition": 'attachment; filename="stuff.txt"'}
>>> print http("""
... POST /plain.txt/@@edit.html HTTP/1.1
... Authorization: Basic mgr:mgrpw
... """, form={"form.data": sio,
... "form.actions.edit": "Edit"}, handle_errors=False)
HTTP/1.1 200 ...
Now, let's request the download view of the file object and check the
result:
>>> print http("""
... GET /plain.txt/@@download HTTP/1.1
... Authorization: Basic mgr:mgrpw
... """, handle_errors=False)
HTTP/1.1 200 Ok
Content-Disposition: attachment; filename="plain.txt"
Content-Length: 8
Content-Type: text/plain;charset=utf-8
new text
If we upload a file that has imprecise content type information (as we
expect from browsers generally, and MSIE most significantly), we can
see that the MIME type machinery will improve the information where
possible:
>>> sio = StringIO("\n"
... "...\n")
>>> sio.filename = "simple.html"
>>> sio.headers = {
... "Content-Type": "text/html; charset=utf-8",
... "Content-Disposition": 'attachment; filename="simple.html"',
... }
>>> print http("""
... POST /@@+/zope.file.File HTTP/1.1
... Authorization: Basic mgr:mgrpw
... """, form={"form.data": sio,
... "form.actions.add": "Add"}, handle_errors=False)
HTTP/1.1 303 ...
Again, we'll request the download view of the file object and check
the result:
>>> print http("""
... GET /simple.html/@@download HTTP/1.1
... Authorization: Basic mgr:mgrpw
... """, handle_errors=False)
HTTP/1.1 200 Ok
Content-Disposition: attachment; filename="simple.html"
Content-Length: 56
Content-Type: application/xhtml+xml;charset=utf-8
...
Further, if a browser is bad and sends a full path as the file name (as
sometimes happens in many browsers, apparently), the name is correctly
truncated and changed.
>>> sio = StringIO("\n"
... "...\n")
>>> sio.filename = r"C:\Documents and Settings\Joe\naughty name.html"
>>> sio.headers = {
... "Content-Type": "text/html; charset=utf-8",
... "Content-Disposition": 'attachment; filename=%s' % sio.filename,
... }
>>> print http("""
... POST /@@+/zope.file.File HTTP/1.1
... Authorization: Basic mgr:mgrpw
... """, form={"form.data": sio,
... "form.actions.add": "Add"}, handle_errors=False)
HTTP/1.1 303 ...
Again, we'll request the download view of the file object and check
the result:
>>> print http("""
... GET /naughty%20name.html/@@download HTTP/1.1
... Authorization: Basic mgr:mgrpw
... """, handle_errors=False)
HTTP/1.1 200 Ok
Content-Disposition: attachment; filename="naughty name.html"
Content-Length: 56
Content-Type: application/xhtml+xml;charset=utf-8
...
In zope.file <= 0.5.0, a redundant ObjectCreatedEvent was fired in the
Upload view. We'll demonstrate that this is no longer the case.
>>> import zope.component
>>> from zope.file.interfaces import IFile
>>> from zope.lifecycleevent import IObjectCreatedEvent
We'll register a subscriber for IObjectCreatedEvent that simply increments
a counter.
>>> count = 0
>>> def inc(*args):
... global count; count += 1
>>> zope.component.provideHandler(inc, (IFile, IObjectCreatedEvent))
>>> print http("""
... POST /@@+/zope.file.File HTTP/1.1
... Authorization: Basic mgr:mgrpw
... """, form={"form.data": sio,
... "form.actions.add": "Add"}, handle_errors=False)
HTTP/1.1 303 ...
The subscriber was called only once.
>>> print count
1
zope.file-0.6.2/src/zope/file/browser.txt 0000664 0001750 0001750 00000002510 11763153771 017740 0 ustar uli uli 0000000 0000000 =====================
Presentation Adapters
=====================
Object size
-----------
The size of the file as presented in the contents view of a container is
provided using an adapter implementing the `zope.size.interfaces.ISized`
interface. Such an adapter is available for the file object.
Let's do some imports and create a new file object:
>>> from zope.file.file import File
>>> from zope.file.browser import Sized
>>> from zope.size.interfaces import ISized
>>> f = File()
>>> f.size
0
>>> s = Sized(f)
>>> ISized.providedBy(s)
True
>>> s.sizeForSorting()
('byte', 0)
>>> s.sizeForDisplay()
u'0 KB'
Let's add some content to the file:
>>> w = f.open('w')
>>> w.write("some text")
>>> w.close()
The sized adapter now reflects the updated size:
>>> s.sizeForSorting()
('byte', 9)
>>> s.sizeForDisplay()
u'1 KB'
Let's try again with a larger file size:
>>> w = f.open('w')
>>> w.write("x" * (1024*1024+10))
>>> w.close()
>>> s.sizeForSorting()
('byte', 1048586)
>>> m = s.sizeForDisplay()
>>> m
u'${size} MB'
>>> m.mapping
{'size': '1.00'}
And still a bigger size:
>>> w = f.open('w')
>>> w.write("x" * 3*512*1024)
>>> w.close()
>>> s.sizeForSorting()
('byte', 1572864)
>>> m = s.sizeForDisplay()
>>> m
u'${size} MB'
>>> m.mapping
{'size': '1.50'}
zope.file-0.6.2/src/zope/file/adapters.txt 0000664 0001750 0001750 00000002746 11763153771 020073 0 ustar uli uli 0000000 0000000 ========
Adapters
========
The `zope.file` package provides some adapters to adapt file-like
objects to `zope.filerepresentation` conform objects. There is a
read-file adapter and a write-file adapter available. We start with a
regular `File` object:
>>> from zope.file.file import File
>>> f = File(parameters=dict(charset='utf-8'))
>>> f.open('w').write("hello")
Now we can turn this file into a read-only file which we can read and
whose size we can get:
>>> from zope.filerepresentation.interfaces import IReadFile, IWriteFile
>>> r = IReadFile(f)
>>> r.read()
'hello'
>>> r.size()
5
Writing to this read-only file is impossible, as the interface does
not require it:
>>> r.write("some more content")
Traceback (most recent call last):
AttributeError: 'ReadFileAdapter' object has no attribute 'write'
With a write-file the opposite happens. We can write but not read:
>>> w = IWriteFile(f)
>>> w.write("some more content")
>>> w.read()
Traceback (most recent call last):
AttributeError: 'WriteFileAdapter' object has no attribute 'read'
The delivered adapters really comply with the promised interfaces:
>>> from zope.interface.verify import verifyClass, verifyObject
>>> from zope.file.adapters import ReadFileAdapter, WriteFileAdapter
>>> verifyClass(IReadFile, ReadFileAdapter)
True
>>> verifyObject(IReadFile, r)
True
>>> verifyClass(IWriteFile, WriteFileAdapter)
True
>>> verifyObject(IWriteFile, w)
True
zope.file-0.6.2/src/zope/file/i18n.py 0000664 0001750 0001750 00000001420 11763153771 016644 0 ustar uli uli 0000000 0000000 ##############################################################################
#
# Copyright (c) 2005 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""I18N support for zope.file.
"""
__docformat__ = "reStructuredText"
import zope.i18nmessageid
_ = zope.i18nmessageid.MessageFactory("zope.file")
zope.file-0.6.2/src/zope/file/__init__.py 0000664 0001750 0001750 00000000046 11763153771 017627 0 ustar uli uli 0000000 0000000 # This directory is a Python package.
zope.file-0.6.2/src/zope/file/download.txt 0000664 0001750 0001750 00000014510 11763153771 020067 0 ustar uli uli 0000000 0000000 ========================
Downloading File Objects
========================
The file content type provides a view used to download the file,
regardless of the browser's default behavior for the content type.
This relies on browser support for the Content-Disposition header.
The download support is provided by two distinct objects: A view that
provides the download support using the information in the content
object, and a result object that can be used to implement a file
download by other views. The view can override the content-type or the
filename suggested to the browser using the standard IResponse.setHeader
method.
Note that result objects are intended to be used once and then
discarded.
Let's start by creating a file object we can use to demonstrate the
download support:
>>> import transaction
>>> from zope.file.file import File
>>> f = File()
>>> getRootFolder()['file'] = f
>>> transaction.commit()
Headers
-------
Now, let's get the headers for this file. We use a utility function called
``getHeaders``:
>>> from zope.file.download import getHeaders
>>> headers = getHeaders(f, contentDisposition='attachment')
Since there's no suggested download filename on the file, the
Content-Disposition header doesn't specify one, but does indicate that
the response body be treated as a file to save rather than to apply
the default handler for the content type:
>>> sorted(headers)
[('Content-Disposition', 'attachment; filename="file"'),
('Content-Length', '0'),
('Content-Type', 'application/octet-stream')]
Note that a default content type of 'application/octet-stream' is
used.
If the file object specifies a content type, that's used in the headers
by default:
>>> f.mimeType = "text/plain"
>>> headers = getHeaders(f, contentDisposition='attachment')
>>> sorted(headers)
[('Content-Disposition', 'attachment; filename="file"'),
('Content-Length', '0'),
('Content-Type', 'text/plain')]
Alternatively, a content type can be specified to ``getHeaders``:
>>> headers = getHeaders(f, contentType="text/xml",
... contentDisposition='attachment')
>>> sorted(headers)
[('Content-Disposition', 'attachment; filename="file"'),
('Content-Length', '0'),
('Content-Type', 'text/xml')]
The filename provided to the browser can be controlled similarly. If
the content object provides one, it will be used by default:
>>> headers = getHeaders(f, contentDisposition='attachment')
>>> sorted(headers)
[('Content-Disposition', 'attachment; filename="file"'),
('Content-Length', '0'),
('Content-Type', 'text/plain')]
Providing an alternate name to ``getHeaders`` overrides the download
name from the file:
>>> headers = getHeaders(f, downloadName="foo.txt",
... contentDisposition='attachment')
>>> sorted(headers)
[('Content-Disposition', 'attachment; filename="foo.txt"'),
('Content-Length', '0'),
('Content-Type', 'text/plain')]
The default Content-Disposition header can be overridden by providing
an argument to ``getHeaders``:
>>> headers = getHeaders(f, contentDisposition="inline")
>>> sorted(headers)
[('Content-Disposition', 'inline; filename="file"'),
('Content-Length', '0'),
('Content-Type', 'text/plain')]
If the `contentDisposition` argument is not provided, none will be
included in the headers:
>>> headers = getHeaders(f)
>>> sorted(headers)
[('Content-Length', '0'),
('Content-Type', 'text/plain')]
Body
----
We use DownloadResult to deliver the content to the browser. Since
there's no data in this file, there are no body chunks:
>>> transaction.commit()
>>> from zope.file.download import DownloadResult
>>> result = DownloadResult(f)
>>> list(result)
[]
We still need to see how non-empty files are handled. Let's write
some data to our file object:
>>> w = f.open("w")
>>> w.write("some text")
>>> w.flush()
>>> w.close()
>>> transaction.commit()
Now we can create a result object and see if we get the data we
expect:
>>> result = DownloadResult(f)
>>> L = list(result)
>>> "".join(L)
'some text'
If the body content is really large, the iterator may provide more
than one chunk of data:
>>> w = f.open("w")
>>> w.write("*" * 1024 * 1024)
>>> w.flush()
>>> w.close()
>>> transaction.commit()
>>> result = DownloadResult(f)
>>> L = list(result)
>>> len(L) > 1
True
Once iteration over the body has completed, further iteration will not
yield additional data:
>>> list(result)
[]
The Download View
-----------------
Now that we've seen the ``getHeaders`` function and the result object,
let's take a look at the basic download view that uses them. We'll need
to add a file object where we can get to it using a browser:
>>> f = File()
>>> f.mimeType = "text/plain"
>>> w = f.open("w")
>>> w.write("some text")
>>> w.close()
>>> transaction.commit()
>>> getRootFolder()["abcdefg"] = f
>>> transaction.commit()
Now, let's request the download view of the file object and check the
result:
>>> print http("""
... GET /abcdefg/@@download HTTP/1.1
... Authorization: Basic mgr:mgrpw
... """, handle_errors=False)
HTTP/1.1 200 Ok
Content-Disposition: attachment; filename="abcdefg"
Content-Length: 9
Content-Type: text/plain
some text
The Inline View
---------------
In addition, it is sometimes useful to view the data inline instead of
downloading it. A basic inline view is provided for this use case.
Note that browsers may decide not to display the image when this view
is used and there is not page that it's being loaded into: if this
view is being referenced directly via the URL, the browser may show
nothing:
>>> print http("""
... GET /abcdefg/@@inline HTTP/1.1
... Authorization: Basic mgr:mgrpw
... """, handle_errors=False)
HTTP/1.1 200 Ok
Content-Disposition: inline; filename="abcdefg"
Content-Length: 9
Content-Type: text/plain
some text
The Default Display View
------------------------
This view is similar to the download and inline views, but no content
disposition is specified at all. This lets the browser's default
handling of the data in the current context to be applied:
>>> print http("""
... GET /abcdefg/@@display HTTP/1.1
... Authorization: Basic mgr:mgrpw
... """, handle_errors=False)
HTTP/1.1 200 Ok
Content-Length: 9
Content-Type: text/plain
some text
zope.file-0.6.2/src/zope/file/tests.py 0000664 0001750 0001750 00000002260 11763153771 017232 0 ustar uli uli 0000000 0000000 ##############################################################################
#
# Copyright (c) 2005 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Unit tests for zope.file.
"""
__docformat__ = "reStructuredText"
import doctest
import unittest
from zope.file import testing
def fromDocFile(path):
suite = testing.FunctionalBlobDocFileSuite(path)
suite.layer = testing.ZopeFileLayer
return suite
def test_suite():
return unittest.TestSuite((
doctest.DocFileSuite("README.txt"),
doctest.DocFileSuite("browser.txt"),
fromDocFile("adapters.txt"),
fromDocFile("contenttype.txt"),
fromDocFile("download.txt"),
fromDocFile("upload.txt"),
))
zope.file-0.6.2/src/zope/file/configure.zcml 0000664 0001750 0001750 00000002573 11763153771 020375 0 ustar uli uli 0000000 0000000
zope.file-0.6.2/src/zope/file/testing.py 0000664 0001750 0001750 00000007234 11763153771 017553 0 ustar uli uli 0000000 0000000 ##############################################################################
#
# Copyright (c) 2005 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Functional tests for zope.file.
"""
__docformat__ = "reStructuredText"
import doctest
import os.path
import shutil
import tempfile
import transaction
from ZODB.DB import DB
from ZODB.DemoStorage import DemoStorage
from ZODB.blob import BlobStorage
import zope.app.testing.functional
from zope.app.component.hooks import setSite
import ZODB.interfaces
here = os.path.dirname(os.path.realpath(__file__))
class FunctionalBlobTestSetup(zope.app.testing.functional.FunctionalTestSetup):
temp_dir_name = None
direct_blob_support = False
def setUp(self):
"""Prepares for a functional test case."""
# Tear down the old demo storage (if any) and create a fresh one
transaction.abort()
self.db.close()
storage = DemoStorage("Demo Storage", self.base_storage)
if ZODB.interfaces.IBlobStorage.providedBy(storage):
# at least ZODB 3.9
self.direct_blob_support = True
else:
# make a dir
temp_dir_name = self.temp_dir_name = tempfile.mkdtemp()
# wrap storage with BlobStorage
storage = BlobStorage(temp_dir_name, storage)
self.db = self.app.db = DB(storage)
self.connection = None
def tearDown(self):
"""Cleans up after a functional test case."""
transaction.abort()
if self.connection:
self.connection.close()
self.connection = None
self.db.close()
if not self.direct_blob_support and self.temp_dir_name is not None:
# del dir named '__blob_test__%s' % self.name
shutil.rmtree(self.temp_dir_name, True)
self.temp_dir_name = None
setSite(None)
class ZCMLLayer(zope.app.testing.functional.ZCMLLayer):
def setUp(self):
self.setup = FunctionalBlobTestSetup(self.config_file)
def FunctionalBlobDocFileSuite(*paths, **kw):
globs = kw.setdefault('globs', {})
globs['http'] = zope.app.testing.functional.HTTPCaller()
globs['getRootFolder'] = zope.app.testing.functional.getRootFolder
globs['sync'] = zope.app.testing.functional.sync
kw['package'] = doctest._normalize_module(kw.get('package'))
kwsetUp = kw.get('setUp')
def setUp(test):
FunctionalBlobTestSetup().setUp()
if kwsetUp is not None:
kwsetUp(test)
kw['setUp'] = setUp
kwtearDown = kw.get('tearDown')
def tearDown(test):
if kwtearDown is not None:
kwtearDown(test)
FunctionalBlobTestSetup().tearDown()
kw['tearDown'] = tearDown
if 'optionflags' not in kw:
old = doctest.set_unittest_reportflags(0)
doctest.set_unittest_reportflags(old)
kw['optionflags'] = (old
| doctest.ELLIPSIS
| doctest.REPORT_NDIFF
| doctest.NORMALIZE_WHITESPACE)
suite = doctest.DocFileSuite(*paths, **kw)
suite.layer = zope.app.testing.functional.Functional
return suite
ZopeFileLayer = ZCMLLayer(
os.path.join(here, "ftesting.zcml"), __name__, "ZopeFileLayer")
zope.file-0.6.2/src/zope/file/file.py 0000664 0001750 0001750 00000003404 11763153771 017010 0 ustar uli uli 0000000 0000000 ##############################################################################
#
# Copyright (c) 2005 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Implementation of the file content type.
"""
__docformat__ = "reStructuredText"
import persistent
import zope.location.interfaces
import zope.file.interfaces
import zope.interface
from ZODB.blob import Blob
class File(persistent.Persistent):
zope.interface.implements(
zope.file.interfaces.IFile,
zope.location.interfaces.ILocation)
__name__ = None
__parent__ = None
mimeType = None
_data = ""
size = 0
def __init__(self, mimeType=None, parameters=None):
self.mimeType = mimeType
if parameters is None:
parameters = {}
else:
parameters = dict(parameters)
self.parameters = parameters
self._data = Blob()
fp = self._data.open('w')
fp.write('')
fp.close()
def open(self, mode="r"):
return self._data.open(mode)
def openDetached(self):
return file(self._data.committed(), 'rb')
@property
def size(self):
if self._data == "":
return 0
reader = self.open()
reader.seek(0,2)
size = int(reader.tell())
reader.close()
return size
zope.file-0.6.2/src/zope/file/browser.zcml 0000664 0001750 0001750 00000001727 11763153771 020077 0 ustar uli uli 0000000 0000000
zope.file-0.6.2/src/zope/file/download.py 0000664 0001750 0001750 00000005704 11763153771 017705 0 ustar uli uli 0000000 0000000 ##############################################################################
#
# Copyright (c) 2005 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Download view for files.
"""
__docformat__ = "reStructuredText"
import zope.interface
import zope.mimetype.interfaces
import zope.publisher.browser
import zope.publisher.interfaces.http
import zope.security.proxy
class Download(zope.publisher.browser.BrowserView):
def __call__(self):
for k, v in getHeaders(self.context, contentDisposition="attachment"):
self.request.response.setHeader(k, v)
return DownloadResult(self.context)
class Inline(zope.publisher.browser.BrowserView):
def __call__(self):
for k, v in getHeaders(self.context, contentDisposition="inline"):
self.request.response.setHeader(k, v)
return DownloadResult(self.context)
class Display(zope.publisher.browser.BrowserView):
def __call__(self):
for k, v in getHeaders(self.context):
self.request.response.setHeader(k, v)
return DownloadResult(self.context)
def getHeaders(context, contentType=None, downloadName=None,
contentDisposition=None, contentLength=None):
if not contentType:
cti = zope.mimetype.interfaces.IContentInfo(context, None)
if cti is not None:
contentType = cti.contentType
contentType = contentType or "application/octet-stream"
headers = ("Content-Type", contentType),
downloadName = downloadName or context.__name__
if contentDisposition:
if downloadName:
contentDisposition += (
'; filename="%s"' % downloadName.encode("utf-8")
)
headers += ("Content-Disposition", contentDisposition),
if contentLength is None:
contentLength = context.size
headers += ("Content-Length", str(contentLength)),
return headers
class DownloadResult(object):
"""Result object for a download request."""
zope.interface.implements(zope.publisher.interfaces.http.IResult)
def __init__(self, context):
self._iter = bodyIterator(
zope.security.proxy.removeSecurityProxy(context.openDetached()))
def __iter__(self):
return self._iter
CHUNK_SIZE = 64 * 1024
def bodyIterator(f):
while True:
chunk = f.read(CHUNK_SIZE)
if not chunk:
f.close()
raise StopIteration()
yield chunk
f.close()
zope.file-0.6.2/src/zope/file/event.py 0000664 0001750 0001750 00000002250 11763153771 017210 0 ustar uli uli 0000000 0000000 ##############################################################################
#
# Copyright (c) 2005 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Event support code.
"""
__docformat__ = "reStructuredText"
def updateMimeType(file, event):
"""Update the file's mimeType on a change.
If the current mimeType value is known to be legal for the new
content type, leave it alone. If not, set the mimeType attribute
to the first value from the list of known MIME types for the
content type.
"""
if event.newContentType is not None:
types = event.newContentType.getTaggedValue("mimeTypes")
if file.mimeType not in types:
file.mimeType = types[0]
zope.file-0.6.2/src/zope/file/interfaces.py 0000664 0001750 0001750 00000010145 11763153771 020214 0 ustar uli uli 0000000 0000000 ##############################################################################
#
# Copyright (c) 2005 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Interfaces for zope.file.
These interfaces support efficient access to file data.
"""
__docformat__ = "reStructuredText"
import zope.interface
import zope.mimetype.interfaces
import zope.schema
from zope.file.i18n import _
class IFile(zope.mimetype.interfaces.IContentTypeAware):
"""File content object for Zope."""
def open(mode="r"):
"""Return an object providing access to the file data.
Allowed values for `mode` are 'r' (read); 'w' (write); 'a' (append) and
'r+' (read/write). Other values cause `ValueError` to be raised.
If the file is opened in read mode, an object with an API (but
not necessarily interface) of `IFileReader` is returned; if
opened in write mode, an object with an API of `IFileWriter` is
returned; if in read/write, an object that implements both is
returned.
All readers and writers operate in 'binary' mode.
"""
def openDetached():
"""Return file data disconnected from database connection.
Read access only.
"""
size = zope.schema.Int(
title=_("Size"),
description=_("Size in bytes"),
readonly=True,
required=True,
)
# The remaining interfaces below serve only to document the kind of APIs
# to be expected, as described in IFile.open above.
class IFileAccessor(zope.interface.Interface):
"""Base accessor for `IFileReader` and `IFileWriter`."""
def close():
"""Release the accessor.
Resources held by the accessor are freed, and further use of
the accessor will result in a `ValueError` exception.
"""
mode = zope.schema.BytesLine(
title=_("Open mode passed to the corresponding file's open() method,"
" or the default value if not passed."),
required=True,
)
class IFileReader(IFileAccessor):
"""Read-accessor for file objects.
The methods here mirror the corresponding portions of the API for
Python file objects.
"""
def read(size=-1):
"""Read at most `size` bytes from the reader.
If the size argument is negative or omitted, read all data
until EOF is reached. The bytes are returned as a str. An
empty string is returned when EOF is encountered immediately.
"""
def seek(offset, whence=0):
"""Set the reader's current position.
The `offset` is a number of bytes. The direction and source
of the measurement is determined by the `whence` argument.
The `whence` argument is optional and defaults to 0 (absolute
file positioning); other recognized values are 1 (seek
relative to the current position) and 2 (seek relative to the
end).
There is no return value.
"""
def tell():
"""Return the current position, measured in bytes."""
class IFileWriter(IFileAccessor):
"""Write-accessor for file objects.
The methods here mirror the corresponding portions of the API for
Python file objects.
"""
def flush():
"""Ensure the file object has been updated.
This is used to make sure that any data buffered in the
accessor is stored in the file object, allowing other
accessors to use it.
"""
def write(bytes):
"""Write a string to the file.
Due to buffering, the string may not actually show up in the
file until the `flush()` or `close()` method is called.
There is no return value.
"""
zope.file-0.6.2/src/zope/file/ftesting.zcml 0000664 0001750 0001750 00000002461 11763153771 020233 0 ustar uli uli 0000000 0000000
zope.file-0.6.2/src/zope/file/contenttype.txt 0000664 0001750 0001750 00000006577 11763153771 020652 0 ustar uli uli 0000000 0000000 ==================================
Content type and encoding controls
==================================
Files provide a view that supports controlling the MIME content type
and, where applicable, the content encoding. Content encoding is
applicable based on the specific content type of the file.
Let's demonstrate the behavior of the form with a simple bit of
content. We'll upload a bit of HTML as a sample document:
>>> import StringIO
>>> sio = StringIO.StringIO("A little HTML."
... " There's one 8-bit Latin-1 character: \xd8.")
>>> from zope.testbrowser.testing import Browser
>>> browser = Browser()
>>> browser.addHeader("Authorization", "Basic mgr:mgrpw")
>>> browser.addHeader("Accept-Language", "en-US")
>>> browser.open("http://localhost/@@+/zope.file.File")
>>> ctrl = browser.getControl(name="form.data")
>>> ctrl.mech_control.add_file(
... sio, "text/html", "sample.html")
>>> browser.getControl("Add").click()
We can see that the MIME handlers have marked this as HTML content:
>>> import zope.mimetype.interfaces
>>> import zope.mimetype.types
>>> file = getRootFolder()["sample.html"]
>>> zope.mimetype.types.IContentTypeTextHtml.providedBy(file)
True
It's important to note that this also means the content is encoded
text:
>>> zope.mimetype.interfaces.IContentTypeEncoded.providedBy(file)
True
The "Content Type" page will show us the MIME type and encoding that
have been selected:
>>> browser.getLink("sample.html").click()
>>> browser.getLink("Content Type").click()
>>> browser.getControl(name="form.mimeType").value
['zope.mimetype.types.IContentTypeTextHtml']
The empty string value indicates that we have no encoding
information:
>>> ctrl = browser.getControl(name="form.encoding")
>>> print ctrl.value
['']
Let's now set the encoding value to an old favorite, Latin-1:
>>> ctrl.value = ["iso-8859-1"]
>>> browser.handleErrors = False
>>> browser.getControl("Save").click()
We now see the updated value in the form, and can check the value in
the MIME content-type parameters on the object:
>>> ctrl = browser.getControl(name="form.encoding")
>>> print ctrl.value
['iso-8859-1']
>>> file = getRootFolder()["sample.html"]
>>> file.parameters
{'charset': 'iso-8859-1'}
Something more interesting is that we can now use a non-encoded
content type, and the encoding field will be removed from the form:
>>> ctrl = browser.getControl(name="form.mimeType")
>>> ctrl.value = ["zope.mimetype.types.IContentTypeImageTiff"]
>>> browser.getControl("Save").click()
>>> browser.getControl(name="form.encoding")
Traceback (most recent call last):
...
LookupError: name 'form.encoding'
If we switch back to an encoded type, we see that our encoding wasn't
lost:
>>> ctrl = browser.getControl(name="form.mimeType")
>>> ctrl.value = ["zope.mimetype.types.IContentTypeTextHtml"]
>>> browser.getControl("Save").click()
>>> browser.getControl(name="form.encoding").value
['iso-8859-1']
On the other hand, if we try setting the encoding to something which
simply cannot decode the input data, we get an error message saying
that's not going to work, and no changes are saved:
>>> ctrl = browser.getControl(name="form.encoding")
>>> ctrl.value = ["utf-8"]
>>> browser.getControl("Save").click()
>>> print browser.contents
<...Selected encoding cannot decode document...
zope.file-0.6.2/src/zope/file/contenttype.py 0000664 0001750 0001750 00000016162 11763153771 020452 0 ustar uli uli 0000000 0000000 ##############################################################################
#
# Copyright (c) 2005 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""The 'Content Type' view for files.
"""
__docformat__ = "reStructuredText"
import zope.component
import zope.formlib.form
import zope.formlib.interfaces
import zope.interface
import zope.lifecycleevent
import zope.mimetype.event
import zope.mimetype.interfaces
import zope.mimetype.source
import zope.schema
import zope.security.proxy
from zope import mimetype
from zope.file.i18n import _
def validateCodecUse(file, iface, codec, codec_field):
"""Validate the use of `codec` for the data in `file`.
`iface` is used as the content-type interface for `file`. If
`iface` is None, no validation is performed and no error is
reported (this validation is considered irrelevant).
If `codec` is None, no error is reported, since that indicates
that no codec is known to be accurate for the data in `file`.
`codec_field` is the form field that is used to select the codec;
it is used to generate an error should one be necessary.
Returns a list of widget input errors that should be added to any
other errors the form determines are relevant.
"""
errs = []
if (codec is not None) and (iface is not None):
if iface.extends(mimetype.interfaces.IContentTypeEncoded):
# Need to test that this codec can be used for this
# document:
f = file.open("r")
content_data = f.read()
f.close()
try:
text, consumed = codec.decode(content_data)
if consumed != len(content_data):
raise UnicodeError("not all data decoded")
except UnicodeError:
err = zope.formlib.interfaces.WidgetInputError(
codec_field.__name__,
codec_field.field.title,
"Selected encoding cannot decode document.")
errs.append(err)
return errs
class ContentTypeForm(zope.formlib.form.Form):
mimeType_field = zope.formlib.form.Field(
zope.schema.Choice(
__name__="mimeType",
title=_("Content type"),
description=_("Type of document"),
source=mimetype.source.contentTypeSource,
))
encoding_field = zope.formlib.form.Field(
zope.schema.Choice(
__name__="encoding",
title=_("Encoding"),
description=_("Character data encoding"),
source=mimetype.source.codecSource,
required=False,
))
def get_rendered_encoding(self):
charset = self.context.parameters.get("charset")
if charset:
return zope.component.queryUtility(
mimetype.interfaces.ICharsetCodec, charset)
else:
return None
encoding_field.get_rendered = get_rendered_encoding
def get_rendered_mimeType(self):
# make sure we return the exact `IContentType`-derived
# interface that the context provides; there *must* be only
# one!
ifaces = zope.interface.directlyProvidedBy(
zope.security.proxy.removeSecurityProxy(self.context))
for iface in ifaces:
if mimetype.interfaces.IContentTypeInterface.providedBy(iface):
return iface
return None
mimeType_field.get_rendered = get_rendered_mimeType
def setUpWidgets(self, ignore_request=False):
# We need to re-compute the fields before initializing the widgets
fields = [self.mimeType_field]
if mimetype.interfaces.IContentTypeEncoded.providedBy(self.context):
self.have_encoded = True
fields.append(self.encoding_field)
else:
self.have_encoded = False
self.form_fields = zope.formlib.form.Fields(*fields)
super(ContentTypeForm, self).setUpWidgets(
ignore_request=ignore_request)
def save_validator(self, action, data):
errs = self.validate(None, data)
errs += validateCodecUse(
self.context, data.get("mimeType"), data.get("encoding"),
self.encoding_field)
return errs
@zope.formlib.form.action(_("Save"), validator=save_validator)
def save(self, action, data):
context = self.context
if data.get("mimeType"):
#
# XXX Note that the content type parameters are not
# modified; ideally, the IContentInfo adapter would filter
# the parameters for what makes sense for the current
# content type.
#
iface = data["mimeType"]
unwrapped = zope.security.proxy.removeSecurityProxy(context)
mimetype.event.changeContentType(unwrapped, iface)
# update the encoding, but only if the new content type is encoded
encoded = mimetype.interfaces.IContentTypeEncoded.providedBy(
context)
# We only care about encoding if we're encoded now and were also
# encoded before starting the re
if encoded and self.have_encoded:
codec = data["encoding"]
if codec is None:
# remove any charset found, since the user said it was wrong;
# what it means to have no known charset is that the policy
# (IContentInfo) will have to decide how to treat encoding.
if "charset" in context.parameters:
# We can't just "del" the existing value, since
# the security model does not like that. We can
# set a new value for context.parameters, though.
parameters = dict(context.parameters)
del parameters["charset"]
context.parameters = parameters
else:
if "charset" in context.parameters:
old_codec = zope.component.queryUtility(
mimetype.interfaces.ICharsetCodec,
context.parameters["charset"])
else:
old_codec = None
if getattr(old_codec, "name", None) != codec.name:
# use the preferred charset for the new codec
new_charset = zope.component.getUtility(
mimetype.interfaces.ICodecPreferredCharset,
codec.name)
parameters = dict(context.parameters)
parameters["charset"] = new_charset.name
context.parameters = parameters
zope.event.notify(
zope.lifecycleevent.ObjectModifiedEvent(
zope.security.proxy.removeSecurityProxy(context)))
zope.file-0.6.2/src/zope/file/adapters.py 0000664 0001750 0001750 00000002632 11763153771 017676 0 ustar uli uli 0000000 0000000 ##############################################################################
#
# Copyright (c) 2005 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
from zope import interface, component
import zope.filerepresentation.interfaces
from zope.file import interfaces
class ReadFileAdapter(object):
component.adapts(interfaces.IFile)
interface.implements(zope.filerepresentation.interfaces.IReadFile)
def __init__(self, context):
self.context = context
def size(self):
return self.context.size
def read(self):
f = self.context.open()
val = f.read()
f.close()
return val
class WriteFileAdapter(object):
component.adapts(interfaces.IFile)
interface.implements(zope.filerepresentation.interfaces.IWriteFile)
def __init__(self, context):
self.context = context
def write(self, data):
f = self.context.open('w')
f.write(data)
f.close()
zope.file-0.6.2/src/zope/__init__.py 0000664 0001750 0001750 00000000070 11763153771 016705 0 ustar uli uli 0000000 0000000 __import__('pkg_resources').declare_namespace(__name__)
zope.file-0.6.2/src/zope.file.egg-info/ 0000775 0001750 0001750 00000000000 11763154606 017205 5 ustar uli uli 0000000 0000000 zope.file-0.6.2/src/zope.file.egg-info/requires.txt 0000664 0001750 0001750 00000000626 11763154603 021606 0 ustar uli uli 0000000 0000000 setuptools
ZODB3
zope.component
zope.browser
zope.container
zope.contenttype>=3.5
zope.event
zope.filerepresentation
zope.formlib>=4
zope.i18nmessageid
zope.lifecycleevent
zope.interface
zope.location
zope.mimetype
zope.publisher
zope.schema
zope.security
zope.size
[test]
zope.app.component
zope.app.server
zope.app.testing
zope.app.zcmlfiles
zope.login
zope.password
zope.securitypolicy
zope.testbrowser zope.file-0.6.2/src/zope.file.egg-info/PKG-INFO 0000664 0001750 0001750 00000072145 11763154603 020310 0 ustar uli uli 0000000 0000000 Metadata-Version: 1.0
Name: zope.file
Version: 0.6.2
Summary: Efficient File Implementation for Zope Applications
Home-page: http://cheeseshop.python.org/pypi/zope.file
Author: Zope Corporation and Contributors
Author-email: zope-dev@zope.org
License: ZPL 2.1
Description: The `zope.file` package provides a content object used to store a
file. The interface supports efficient upload and download.
.. contents::
===========
File Object
===========
The `zope.file` package provides a content object used to store a
file. The interface supports efficient upload and download. Let's
create an instance:
>>> from zope.file.file import File
>>> f = File()
The object provides a limited number of data attributes. The
`mimeType` attribute is used to store the preferred MIME
content-type value for the data:
>>> f.mimeType
>>> f.mimeType = "text/plain"
>>> f.mimeType
'text/plain'
>>> f.mimeType = "application/postscript"
>>> f.mimeType
'application/postscript'
The `parameters` attribute is a mapping used to store the content-type
parameters. This is where encoding information can be found when
applicable (and available):
>>> f.parameters
{}
>>> f.parameters["charset"] = "us-ascii"
>>> f.parameters["charset"]
'us-ascii'
Both, `parameters` and `mimeType` can optionally also be set when
creating a `File` object:
>>> f2 = File(mimeType = "application/octet-stream",
... parameters = dict(charset = "utf-8"))
>>> f2.mimeType
'application/octet-stream'
>>> f2.parameters["charset"]
'utf-8'
File objects also sport a `size` attribute that provides the number of
bytes in the file:
>>> f.size
0
The object supports efficient upload and download by providing all
access to content data through accessor objects that provide (subsets
of) Python's file API.
A file that hasn't been written to is empty. We can get a reader by calling
`open()`. Note that all blobs are binary, thus the mode always contains a
'b':
>>> r = f.open("r")
>>> r.mode
'rb'
The `read()` method can be called with a non-negative integer argument
to specify how many bytes to read, or with a negative or omitted
argument to read to the end of the file:
>>> r.read(10)
''
>>> r.read()
''
>>> r.read(-1)
''
Once the accessor has been closed, we can no longer read from it:
>>> r.close()
>>> r.read()
Traceback (most recent call last):
ValueError: I/O operation on closed file
We'll see that readers are more interesting once there's data in the
file object.
Data is added by using a writer, which is also created using the
`open()` method on the file, but requesting a write file mode:
>>> w = f.open("w")
>>> w.mode
'wb'
The `write()` method is used to add data to the file, but note that
the data may be buffered in the writer:
>>> w.write("some text ")
>>> w.write("more text")
The `flush()` method ensure that the data written so far is written to
the file object:
>>> w.flush()
We need to close the file first before determining its file size
>>> w.close()
>>> f.size
19
We can now use a reader to see that the data has been written to the
file:
>>> w = f.open("w")
>>> w.write('some text more text')
>>> w.write(" still more")
>>> w.close()
>>> f.size
30
Now create a new reader and let's perform some seek operations.
>>> r = f.open()
The reader also has a `seek()` method that can be used to back up or
skip forward in the data stream. Simply passing an offset argument,
we see that the current position is moved to that offset from the
start of the file:
>>> r.seek(20)
>>> r.read()
'still more'
That's equivalent to passing 0 as the `whence` argument:
>>> r.seek(20, 0)
>>> r.read()
'still more'
We can skip backward and forward relative to the current position by
passing 1 for `whence`:
>>> r.seek(-10, 1)
>>> r.read(5)
'still'
>>> r.seek(2, 1)
>>> r.read()
'ore'
We can skip to some position backward from the end of the file using
the value 2 for `whence`:
>>> r.seek(-10, 2)
>>> r.read()
'still more'
>>> r.seek(0)
>>> r.seek(-4, 2)
>>> r.read()
'more'
>>> r.close()
Attempting to write to a closed writer raises an exception:
>>> w = f.open('w')
>>> w.close()
>>> w.write('foobar')
Traceback (most recent call last):
ValueError: I/O operation on closed file
Similarly, using `seek()` or `tell()` on a closed reader raises an
exception:
>>> r.close()
>>> r.seek(0)
Traceback (most recent call last):
ValueError: I/O operation on closed file
>>> r.tell()
Traceback (most recent call last):
ValueError: I/O operation on closed file
========================
Downloading File Objects
========================
The file content type provides a view used to download the file,
regardless of the browser's default behavior for the content type.
This relies on browser support for the Content-Disposition header.
The download support is provided by two distinct objects: A view that
provides the download support using the information in the content
object, and a result object that can be used to implement a file
download by other views. The view can override the content-type or the
filename suggested to the browser using the standard IResponse.setHeader
method.
Note that result objects are intended to be used once and then
discarded.
Let's start by creating a file object we can use to demonstrate the
download support:
>>> import transaction
>>> from zope.file.file import File
>>> f = File()
>>> getRootFolder()['file'] = f
>>> transaction.commit()
Headers
-------
Now, let's get the headers for this file. We use a utility function called
``getHeaders``:
>>> from zope.file.download import getHeaders
>>> headers = getHeaders(f, contentDisposition='attachment')
Since there's no suggested download filename on the file, the
Content-Disposition header doesn't specify one, but does indicate that
the response body be treated as a file to save rather than to apply
the default handler for the content type:
>>> sorted(headers)
[('Content-Disposition', 'attachment; filename="file"'),
('Content-Length', '0'),
('Content-Type', 'application/octet-stream')]
Note that a default content type of 'application/octet-stream' is
used.
If the file object specifies a content type, that's used in the headers
by default:
>>> f.mimeType = "text/plain"
>>> headers = getHeaders(f, contentDisposition='attachment')
>>> sorted(headers)
[('Content-Disposition', 'attachment; filename="file"'),
('Content-Length', '0'),
('Content-Type', 'text/plain')]
Alternatively, a content type can be specified to ``getHeaders``:
>>> headers = getHeaders(f, contentType="text/xml",
... contentDisposition='attachment')
>>> sorted(headers)
[('Content-Disposition', 'attachment; filename="file"'),
('Content-Length', '0'),
('Content-Type', 'text/xml')]
The filename provided to the browser can be controlled similarly. If
the content object provides one, it will be used by default:
>>> headers = getHeaders(f, contentDisposition='attachment')
>>> sorted(headers)
[('Content-Disposition', 'attachment; filename="file"'),
('Content-Length', '0'),
('Content-Type', 'text/plain')]
Providing an alternate name to ``getHeaders`` overrides the download
name from the file:
>>> headers = getHeaders(f, downloadName="foo.txt",
... contentDisposition='attachment')
>>> sorted(headers)
[('Content-Disposition', 'attachment; filename="foo.txt"'),
('Content-Length', '0'),
('Content-Type', 'text/plain')]
The default Content-Disposition header can be overridden by providing
an argument to ``getHeaders``:
>>> headers = getHeaders(f, contentDisposition="inline")
>>> sorted(headers)
[('Content-Disposition', 'inline; filename="file"'),
('Content-Length', '0'),
('Content-Type', 'text/plain')]
If the `contentDisposition` argument is not provided, none will be
included in the headers:
>>> headers = getHeaders(f)
>>> sorted(headers)
[('Content-Length', '0'),
('Content-Type', 'text/plain')]
Body
----
We use DownloadResult to deliver the content to the browser. Since
there's no data in this file, there are no body chunks:
>>> transaction.commit()
>>> from zope.file.download import DownloadResult
>>> result = DownloadResult(f)
>>> list(result)
[]
We still need to see how non-empty files are handled. Let's write
some data to our file object:
>>> w = f.open("w")
>>> w.write("some text")
>>> w.flush()
>>> w.close()
>>> transaction.commit()
Now we can create a result object and see if we get the data we
expect:
>>> result = DownloadResult(f)
>>> L = list(result)
>>> "".join(L)
'some text'
If the body content is really large, the iterator may provide more
than one chunk of data:
>>> w = f.open("w")
>>> w.write("*" * 1024 * 1024)
>>> w.flush()
>>> w.close()
>>> transaction.commit()
>>> result = DownloadResult(f)
>>> L = list(result)
>>> len(L) > 1
True
Once iteration over the body has completed, further iteration will not
yield additional data:
>>> list(result)
[]
The Download View
-----------------
Now that we've seen the ``getHeaders`` function and the result object,
let's take a look at the basic download view that uses them. We'll need
to add a file object where we can get to it using a browser:
>>> f = File()
>>> f.mimeType = "text/plain"
>>> w = f.open("w")
>>> w.write("some text")
>>> w.close()
>>> transaction.commit()
>>> getRootFolder()["abcdefg"] = f
>>> transaction.commit()
Now, let's request the download view of the file object and check the
result:
>>> print http("""
... GET /abcdefg/@@download HTTP/1.1
... Authorization: Basic mgr:mgrpw
... """, handle_errors=False)
HTTP/1.1 200 Ok
Content-Disposition: attachment; filename="abcdefg"
Content-Length: 9
Content-Type: text/plain
some text
The Inline View
---------------
In addition, it is sometimes useful to view the data inline instead of
downloading it. A basic inline view is provided for this use case.
Note that browsers may decide not to display the image when this view
is used and there is not page that it's being loaded into: if this
view is being referenced directly via the URL, the browser may show
nothing:
>>> print http("""
... GET /abcdefg/@@inline HTTP/1.1
... Authorization: Basic mgr:mgrpw
... """, handle_errors=False)
HTTP/1.1 200 Ok
Content-Disposition: inline; filename="abcdefg"
Content-Length: 9
Content-Type: text/plain
some text
The Default Display View
------------------------
This view is similar to the download and inline views, but no content
disposition is specified at all. This lets the browser's default
handling of the data in the current context to be applied:
>>> print http("""
... GET /abcdefg/@@display HTTP/1.1
... Authorization: Basic mgr:mgrpw
... """, handle_errors=False)
HTTP/1.1 200 Ok
Content-Length: 9
Content-Type: text/plain
some text
====================
Uploading a new file
====================
There's a simple view for uploading a new file. Let's try it:
>>> from StringIO import StringIO
>>> sio = StringIO("some text")
>>> sio.filename = "plain.txt"
>>> sio.headers = {"Content-Type": "text/plain; charset=utf-8",
... "Content-Disposition": 'attachment; filename="plain.txt"'}
>>> print http("""
... POST /@@+/zope.file.File HTTP/1.1
... Authorization: Basic mgr:mgrpw
... """, form={"form.data": sio,
... "form.actions.add": "Add"}, handle_errors=False)
HTTP/1.1 303 ...
Now, let's request the download view of the file object and check the
result:
>>> print http("""
... GET /plain.txt/@@download HTTP/1.1
... Authorization: Basic mgr:mgrpw
... """, handle_errors=False)
HTTP/1.1 200 Ok
Content-Disposition: attachment; filename="plain.txt"
Content-Length: 9
Content-Type: text/plain;charset=utf-8
some text
We'll peek into the database to make sure the object implements the
expected MIME type interface:
>>> from zope.mimetype import types
>>> ob = getRootFolder()["plain.txt"]
>>> types.IContentTypeTextPlain.providedBy(ob)
True
We can upload new data into our file object as well:
>>> sio = StringIO("new text")
>>> sio.filename = "stuff.txt"
>>> sio.headers = {"Content-Type": "text/plain; charset=utf-8",
... "Content-Disposition": 'attachment; filename="stuff.txt"'}
>>> print http("""
... POST /plain.txt/@@edit.html HTTP/1.1
... Authorization: Basic mgr:mgrpw
... """, form={"form.data": sio,
... "form.actions.edit": "Edit"}, handle_errors=False)
HTTP/1.1 200 ...
Now, let's request the download view of the file object and check the
result:
>>> print http("""
... GET /plain.txt/@@download HTTP/1.1
... Authorization: Basic mgr:mgrpw
... """, handle_errors=False)
HTTP/1.1 200 Ok
Content-Disposition: attachment; filename="plain.txt"
Content-Length: 8
Content-Type: text/plain;charset=utf-8
new text
If we upload a file that has imprecise content type information (as we
expect from browsers generally, and MSIE most significantly), we can
see that the MIME type machinery will improve the information where
possible:
>>> sio = StringIO("\n"
... "...\n")
>>> sio.filename = "simple.html"
>>> sio.headers = {
... "Content-Type": "text/html; charset=utf-8",
... "Content-Disposition": 'attachment; filename="simple.html"',
... }
>>> print http("""
... POST /@@+/zope.file.File HTTP/1.1
... Authorization: Basic mgr:mgrpw
... """, form={"form.data": sio,
... "form.actions.add": "Add"}, handle_errors=False)
HTTP/1.1 303 ...
Again, we'll request the download view of the file object and check
the result:
>>> print http("""
... GET /simple.html/@@download HTTP/1.1
... Authorization: Basic mgr:mgrpw
... """, handle_errors=False)
HTTP/1.1 200 Ok
Content-Disposition: attachment; filename="simple.html"
Content-Length: 56
Content-Type: application/xhtml+xml;charset=utf-8
...
Further, if a browser is bad and sends a full path as the file name (as
sometimes happens in many browsers, apparently), the name is correctly
truncated and changed.
>>> sio = StringIO("\n"
... "...\n")
>>> sio.filename = r"C:\Documents and Settings\Joe\naughty name.html"
>>> sio.headers = {
... "Content-Type": "text/html; charset=utf-8",
... "Content-Disposition": 'attachment; filename=%s' % sio.filename,
... }
>>> print http("""
... POST /@@+/zope.file.File HTTP/1.1
... Authorization: Basic mgr:mgrpw
... """, form={"form.data": sio,
... "form.actions.add": "Add"}, handle_errors=False)
HTTP/1.1 303 ...
Again, we'll request the download view of the file object and check
the result:
>>> print http("""
... GET /naughty%20name.html/@@download HTTP/1.1
... Authorization: Basic mgr:mgrpw
... """, handle_errors=False)
HTTP/1.1 200 Ok
Content-Disposition: attachment; filename="naughty name.html"
Content-Length: 56
Content-Type: application/xhtml+xml;charset=utf-8
...
In zope.file <= 0.5.0, a redundant ObjectCreatedEvent was fired in the
Upload view. We'll demonstrate that this is no longer the case.
>>> import zope.component
>>> from zope.file.interfaces import IFile
>>> from zope.lifecycleevent import IObjectCreatedEvent
We'll register a subscriber for IObjectCreatedEvent that simply increments
a counter.
>>> count = 0
>>> def inc(*args):
... global count; count += 1
>>> zope.component.provideHandler(inc, (IFile, IObjectCreatedEvent))
>>> print http("""
... POST /@@+/zope.file.File HTTP/1.1
... Authorization: Basic mgr:mgrpw
... """, form={"form.data": sio,
... "form.actions.add": "Add"}, handle_errors=False)
HTTP/1.1 303 ...
The subscriber was called only once.
>>> print count
1
==================================
Content type and encoding controls
==================================
Files provide a view that supports controlling the MIME content type
and, where applicable, the content encoding. Content encoding is
applicable based on the specific content type of the file.
Let's demonstrate the behavior of the form with a simple bit of
content. We'll upload a bit of HTML as a sample document:
>>> import StringIO
>>> sio = StringIO.StringIO("A little HTML."
... " There's one 8-bit Latin-1 character: \xd8.")
>>> from zope.testbrowser.testing import Browser
>>> browser = Browser()
>>> browser.addHeader("Authorization", "Basic mgr:mgrpw")
>>> browser.addHeader("Accept-Language", "en-US")
>>> browser.open("http://localhost/@@+/zope.file.File")
>>> ctrl = browser.getControl(name="form.data")
>>> ctrl.mech_control.add_file(
... sio, "text/html", "sample.html")
>>> browser.getControl("Add").click()
We can see that the MIME handlers have marked this as HTML content:
>>> import zope.mimetype.interfaces
>>> import zope.mimetype.types
>>> file = getRootFolder()["sample.html"]
>>> zope.mimetype.types.IContentTypeTextHtml.providedBy(file)
True
It's important to note that this also means the content is encoded
text:
>>> zope.mimetype.interfaces.IContentTypeEncoded.providedBy(file)
True
The "Content Type" page will show us the MIME type and encoding that
have been selected:
>>> browser.getLink("sample.html").click()
>>> browser.getLink("Content Type").click()
>>> browser.getControl(name="form.mimeType").value
['zope.mimetype.types.IContentTypeTextHtml']
The empty string value indicates that we have no encoding
information:
>>> ctrl = browser.getControl(name="form.encoding")
>>> print ctrl.value
['']
Let's now set the encoding value to an old favorite, Latin-1:
>>> ctrl.value = ["iso-8859-1"]
>>> browser.handleErrors = False
>>> browser.getControl("Save").click()
We now see the updated value in the form, and can check the value in
the MIME content-type parameters on the object:
>>> ctrl = browser.getControl(name="form.encoding")
>>> print ctrl.value
['iso-8859-1']
>>> file = getRootFolder()["sample.html"]
>>> file.parameters
{'charset': 'iso-8859-1'}
Something more interesting is that we can now use a non-encoded
content type, and the encoding field will be removed from the form:
>>> ctrl = browser.getControl(name="form.mimeType")
>>> ctrl.value = ["zope.mimetype.types.IContentTypeImageTiff"]
>>> browser.getControl("Save").click()
>>> browser.getControl(name="form.encoding")
Traceback (most recent call last):
...
LookupError: name 'form.encoding'
If we switch back to an encoded type, we see that our encoding wasn't
lost:
>>> ctrl = browser.getControl(name="form.mimeType")
>>> ctrl.value = ["zope.mimetype.types.IContentTypeTextHtml"]
>>> browser.getControl("Save").click()
>>> browser.getControl(name="form.encoding").value
['iso-8859-1']
On the other hand, if we try setting the encoding to something which
simply cannot decode the input data, we get an error message saying
that's not going to work, and no changes are saved:
>>> ctrl = browser.getControl(name="form.encoding")
>>> ctrl.value = ["utf-8"]
>>> browser.getControl("Save").click()
>>> print browser.contents
<...Selected encoding cannot decode document...
=====================
Presentation Adapters
=====================
Object size
-----------
The size of the file as presented in the contents view of a container is
provided using an adapter implementing the `zope.size.interfaces.ISized`
interface. Such an adapter is available for the file object.
Let's do some imports and create a new file object:
>>> from zope.file.file import File
>>> from zope.file.browser import Sized
>>> from zope.size.interfaces import ISized
>>> f = File()
>>> f.size
0
>>> s = Sized(f)
>>> ISized.providedBy(s)
True
>>> s.sizeForSorting()
('byte', 0)
>>> s.sizeForDisplay()
u'0 KB'
Let's add some content to the file:
>>> w = f.open('w')
>>> w.write("some text")
>>> w.close()
The sized adapter now reflects the updated size:
>>> s.sizeForSorting()
('byte', 9)
>>> s.sizeForDisplay()
u'1 KB'
Let's try again with a larger file size:
>>> w = f.open('w')
>>> w.write("x" * (1024*1024+10))
>>> w.close()
>>> s.sizeForSorting()
('byte', 1048586)
>>> m = s.sizeForDisplay()
>>> m
u'${size} MB'
>>> m.mapping
{'size': '1.00'}
And still a bigger size:
>>> w = f.open('w')
>>> w.write("x" * 3*512*1024)
>>> w.close()
>>> s.sizeForSorting()
('byte', 1572864)
>>> m = s.sizeForDisplay()
>>> m
u'${size} MB'
>>> m.mapping
{'size': '1.50'}
=======
CHANGES
=======
0.6.2 (2012-06-04)
------------------
- Moved menu-oriented registrations into new menus.zcml. This is now
loaded if zope.app.zcmlfiles is available only.
- Increase test coverage.
0.6.1 (2012-01-26)
------------------
- Declared more dependencies.
0.6.0 (2010-09-16)
------------------
- Bug fix: remove duplicate firing of ObjectCreatedEvent in
zope.file.upload.Upload (the event is already fired in its base class,
zope.formlib.form.AddForm).
- Move browser-related zcml to `browser.zcml` so that it easier for
applications to exclude it.
- Import content-type parser from zope.contenttype, adding a dependency on
that package.
- Removed undeclared dependency on zope.app.container, depend on zope.browser.
- Using Python's ``doctest`` module instead of deprecated
``zope.testing.doctest``.
0.5.0 (2009-07-23)
------------------
- Change package's mailing list address to zope-dev at zope.org instead
of the retired one.
- Made tests compatible with ZODB 3.9.
- Removed not needed install requirement declarations.
0.4.0 (2009-01-31)
------------------
- `openDetached` is now protected by zope.View instead of zope.ManageContent.
- Use zope.container instead of zope.app.container.
0.3.0 (2007-11-01)
------------------
- Package data update.
0.2.0 (2007-04-18)
------------------
- Fix code for Publisher version 3.4.
0.1.0 (2007-04-18)
------------------
- Initial release.
Keywords: zope3 web html ui file pattern
Platform: UNKNOWN
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Web Environment
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Zope Public License
Classifier: Programming Language :: Python
Classifier: Natural Language :: English
Classifier: Operating System :: OS Independent
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Framework :: Zope3
zope.file-0.6.2/src/zope.file.egg-info/dependency_links.txt 0000664 0001750 0001750 00000000001 11763154603 023250 0 ustar uli uli 0000000 0000000
zope.file-0.6.2/src/zope.file.egg-info/SOURCES.txt 0000664 0001750 0001750 00000001646 11763154603 021075 0 ustar uli uli 0000000 0000000 CHANGES.txt
README.txt
ZopePublicLicense.txt
bootstrap.py
buildout.cfg
setup.py
src/zope/__init__.py
src/zope.file.egg-info/PKG-INFO
src/zope.file.egg-info/SOURCES.txt
src/zope.file.egg-info/dependency_links.txt
src/zope.file.egg-info/namespace_packages.txt
src/zope.file.egg-info/not-zip-safe
src/zope.file.egg-info/requires.txt
src/zope.file.egg-info/top_level.txt
src/zope/file/README.txt
src/zope/file/__init__.py
src/zope/file/adapters.py
src/zope/file/adapters.txt
src/zope/file/browser.py
src/zope/file/browser.txt
src/zope/file/browser.zcml
src/zope/file/configure.zcml
src/zope/file/contenttype.py
src/zope/file/contenttype.txt
src/zope/file/download.py
src/zope/file/download.txt
src/zope/file/event.py
src/zope/file/file.py
src/zope/file/ftesting.zcml
src/zope/file/i18n.py
src/zope/file/interfaces.py
src/zope/file/menus.zcml
src/zope/file/testing.py
src/zope/file/tests.py
src/zope/file/upload.py
src/zope/file/upload.txt zope.file-0.6.2/src/zope.file.egg-info/top_level.txt 0000664 0001750 0001750 00000000005 11763154603 021727 0 ustar uli uli 0000000 0000000 zope
zope.file-0.6.2/src/zope.file.egg-info/not-zip-safe 0000664 0001750 0001750 00000000001 11763154052 021426 0 ustar uli uli 0000000 0000000
zope.file-0.6.2/src/zope.file.egg-info/namespace_packages.txt 0000664 0001750 0001750 00000000005 11763154603 023530 0 ustar uli uli 0000000 0000000 zope
zope.file-0.6.2/PKG-INFO 0000664 0001750 0001750 00000072145 11763154606 014137 0 ustar uli uli 0000000 0000000 Metadata-Version: 1.0
Name: zope.file
Version: 0.6.2
Summary: Efficient File Implementation for Zope Applications
Home-page: http://cheeseshop.python.org/pypi/zope.file
Author: Zope Corporation and Contributors
Author-email: zope-dev@zope.org
License: ZPL 2.1
Description: The `zope.file` package provides a content object used to store a
file. The interface supports efficient upload and download.
.. contents::
===========
File Object
===========
The `zope.file` package provides a content object used to store a
file. The interface supports efficient upload and download. Let's
create an instance:
>>> from zope.file.file import File
>>> f = File()
The object provides a limited number of data attributes. The
`mimeType` attribute is used to store the preferred MIME
content-type value for the data:
>>> f.mimeType
>>> f.mimeType = "text/plain"
>>> f.mimeType
'text/plain'
>>> f.mimeType = "application/postscript"
>>> f.mimeType
'application/postscript'
The `parameters` attribute is a mapping used to store the content-type
parameters. This is where encoding information can be found when
applicable (and available):
>>> f.parameters
{}
>>> f.parameters["charset"] = "us-ascii"
>>> f.parameters["charset"]
'us-ascii'
Both, `parameters` and `mimeType` can optionally also be set when
creating a `File` object:
>>> f2 = File(mimeType = "application/octet-stream",
... parameters = dict(charset = "utf-8"))
>>> f2.mimeType
'application/octet-stream'
>>> f2.parameters["charset"]
'utf-8'
File objects also sport a `size` attribute that provides the number of
bytes in the file:
>>> f.size
0
The object supports efficient upload and download by providing all
access to content data through accessor objects that provide (subsets
of) Python's file API.
A file that hasn't been written to is empty. We can get a reader by calling
`open()`. Note that all blobs are binary, thus the mode always contains a
'b':
>>> r = f.open("r")
>>> r.mode
'rb'
The `read()` method can be called with a non-negative integer argument
to specify how many bytes to read, or with a negative or omitted
argument to read to the end of the file:
>>> r.read(10)
''
>>> r.read()
''
>>> r.read(-1)
''
Once the accessor has been closed, we can no longer read from it:
>>> r.close()
>>> r.read()
Traceback (most recent call last):
ValueError: I/O operation on closed file
We'll see that readers are more interesting once there's data in the
file object.
Data is added by using a writer, which is also created using the
`open()` method on the file, but requesting a write file mode:
>>> w = f.open("w")
>>> w.mode
'wb'
The `write()` method is used to add data to the file, but note that
the data may be buffered in the writer:
>>> w.write("some text ")
>>> w.write("more text")
The `flush()` method ensure that the data written so far is written to
the file object:
>>> w.flush()
We need to close the file first before determining its file size
>>> w.close()
>>> f.size
19
We can now use a reader to see that the data has been written to the
file:
>>> w = f.open("w")
>>> w.write('some text more text')
>>> w.write(" still more")
>>> w.close()
>>> f.size
30
Now create a new reader and let's perform some seek operations.
>>> r = f.open()
The reader also has a `seek()` method that can be used to back up or
skip forward in the data stream. Simply passing an offset argument,
we see that the current position is moved to that offset from the
start of the file:
>>> r.seek(20)
>>> r.read()
'still more'
That's equivalent to passing 0 as the `whence` argument:
>>> r.seek(20, 0)
>>> r.read()
'still more'
We can skip backward and forward relative to the current position by
passing 1 for `whence`:
>>> r.seek(-10, 1)
>>> r.read(5)
'still'
>>> r.seek(2, 1)
>>> r.read()
'ore'
We can skip to some position backward from the end of the file using
the value 2 for `whence`:
>>> r.seek(-10, 2)
>>> r.read()
'still more'
>>> r.seek(0)
>>> r.seek(-4, 2)
>>> r.read()
'more'
>>> r.close()
Attempting to write to a closed writer raises an exception:
>>> w = f.open('w')
>>> w.close()
>>> w.write('foobar')
Traceback (most recent call last):
ValueError: I/O operation on closed file
Similarly, using `seek()` or `tell()` on a closed reader raises an
exception:
>>> r.close()
>>> r.seek(0)
Traceback (most recent call last):
ValueError: I/O operation on closed file
>>> r.tell()
Traceback (most recent call last):
ValueError: I/O operation on closed file
========================
Downloading File Objects
========================
The file content type provides a view used to download the file,
regardless of the browser's default behavior for the content type.
This relies on browser support for the Content-Disposition header.
The download support is provided by two distinct objects: A view that
provides the download support using the information in the content
object, and a result object that can be used to implement a file
download by other views. The view can override the content-type or the
filename suggested to the browser using the standard IResponse.setHeader
method.
Note that result objects are intended to be used once and then
discarded.
Let's start by creating a file object we can use to demonstrate the
download support:
>>> import transaction
>>> from zope.file.file import File
>>> f = File()
>>> getRootFolder()['file'] = f
>>> transaction.commit()
Headers
-------
Now, let's get the headers for this file. We use a utility function called
``getHeaders``:
>>> from zope.file.download import getHeaders
>>> headers = getHeaders(f, contentDisposition='attachment')
Since there's no suggested download filename on the file, the
Content-Disposition header doesn't specify one, but does indicate that
the response body be treated as a file to save rather than to apply
the default handler for the content type:
>>> sorted(headers)
[('Content-Disposition', 'attachment; filename="file"'),
('Content-Length', '0'),
('Content-Type', 'application/octet-stream')]
Note that a default content type of 'application/octet-stream' is
used.
If the file object specifies a content type, that's used in the headers
by default:
>>> f.mimeType = "text/plain"
>>> headers = getHeaders(f, contentDisposition='attachment')
>>> sorted(headers)
[('Content-Disposition', 'attachment; filename="file"'),
('Content-Length', '0'),
('Content-Type', 'text/plain')]
Alternatively, a content type can be specified to ``getHeaders``:
>>> headers = getHeaders(f, contentType="text/xml",
... contentDisposition='attachment')
>>> sorted(headers)
[('Content-Disposition', 'attachment; filename="file"'),
('Content-Length', '0'),
('Content-Type', 'text/xml')]
The filename provided to the browser can be controlled similarly. If
the content object provides one, it will be used by default:
>>> headers = getHeaders(f, contentDisposition='attachment')
>>> sorted(headers)
[('Content-Disposition', 'attachment; filename="file"'),
('Content-Length', '0'),
('Content-Type', 'text/plain')]
Providing an alternate name to ``getHeaders`` overrides the download
name from the file:
>>> headers = getHeaders(f, downloadName="foo.txt",
... contentDisposition='attachment')
>>> sorted(headers)
[('Content-Disposition', 'attachment; filename="foo.txt"'),
('Content-Length', '0'),
('Content-Type', 'text/plain')]
The default Content-Disposition header can be overridden by providing
an argument to ``getHeaders``:
>>> headers = getHeaders(f, contentDisposition="inline")
>>> sorted(headers)
[('Content-Disposition', 'inline; filename="file"'),
('Content-Length', '0'),
('Content-Type', 'text/plain')]
If the `contentDisposition` argument is not provided, none will be
included in the headers:
>>> headers = getHeaders(f)
>>> sorted(headers)
[('Content-Length', '0'),
('Content-Type', 'text/plain')]
Body
----
We use DownloadResult to deliver the content to the browser. Since
there's no data in this file, there are no body chunks:
>>> transaction.commit()
>>> from zope.file.download import DownloadResult
>>> result = DownloadResult(f)
>>> list(result)
[]
We still need to see how non-empty files are handled. Let's write
some data to our file object:
>>> w = f.open("w")
>>> w.write("some text")
>>> w.flush()
>>> w.close()
>>> transaction.commit()
Now we can create a result object and see if we get the data we
expect:
>>> result = DownloadResult(f)
>>> L = list(result)
>>> "".join(L)
'some text'
If the body content is really large, the iterator may provide more
than one chunk of data:
>>> w = f.open("w")
>>> w.write("*" * 1024 * 1024)
>>> w.flush()
>>> w.close()
>>> transaction.commit()
>>> result = DownloadResult(f)
>>> L = list(result)
>>> len(L) > 1
True
Once iteration over the body has completed, further iteration will not
yield additional data:
>>> list(result)
[]
The Download View
-----------------
Now that we've seen the ``getHeaders`` function and the result object,
let's take a look at the basic download view that uses them. We'll need
to add a file object where we can get to it using a browser:
>>> f = File()
>>> f.mimeType = "text/plain"
>>> w = f.open("w")
>>> w.write("some text")
>>> w.close()
>>> transaction.commit()
>>> getRootFolder()["abcdefg"] = f
>>> transaction.commit()
Now, let's request the download view of the file object and check the
result:
>>> print http("""
... GET /abcdefg/@@download HTTP/1.1
... Authorization: Basic mgr:mgrpw
... """, handle_errors=False)
HTTP/1.1 200 Ok
Content-Disposition: attachment; filename="abcdefg"
Content-Length: 9
Content-Type: text/plain
some text
The Inline View
---------------
In addition, it is sometimes useful to view the data inline instead of
downloading it. A basic inline view is provided for this use case.
Note that browsers may decide not to display the image when this view
is used and there is not page that it's being loaded into: if this
view is being referenced directly via the URL, the browser may show
nothing:
>>> print http("""
... GET /abcdefg/@@inline HTTP/1.1
... Authorization: Basic mgr:mgrpw
... """, handle_errors=False)
HTTP/1.1 200 Ok
Content-Disposition: inline; filename="abcdefg"
Content-Length: 9
Content-Type: text/plain
some text
The Default Display View
------------------------
This view is similar to the download and inline views, but no content
disposition is specified at all. This lets the browser's default
handling of the data in the current context to be applied:
>>> print http("""
... GET /abcdefg/@@display HTTP/1.1
... Authorization: Basic mgr:mgrpw
... """, handle_errors=False)
HTTP/1.1 200 Ok
Content-Length: 9
Content-Type: text/plain
some text
====================
Uploading a new file
====================
There's a simple view for uploading a new file. Let's try it:
>>> from StringIO import StringIO
>>> sio = StringIO("some text")
>>> sio.filename = "plain.txt"
>>> sio.headers = {"Content-Type": "text/plain; charset=utf-8",
... "Content-Disposition": 'attachment; filename="plain.txt"'}
>>> print http("""
... POST /@@+/zope.file.File HTTP/1.1
... Authorization: Basic mgr:mgrpw
... """, form={"form.data": sio,
... "form.actions.add": "Add"}, handle_errors=False)
HTTP/1.1 303 ...
Now, let's request the download view of the file object and check the
result:
>>> print http("""
... GET /plain.txt/@@download HTTP/1.1
... Authorization: Basic mgr:mgrpw
... """, handle_errors=False)
HTTP/1.1 200 Ok
Content-Disposition: attachment; filename="plain.txt"
Content-Length: 9
Content-Type: text/plain;charset=utf-8
some text
We'll peek into the database to make sure the object implements the
expected MIME type interface:
>>> from zope.mimetype import types
>>> ob = getRootFolder()["plain.txt"]
>>> types.IContentTypeTextPlain.providedBy(ob)
True
We can upload new data into our file object as well:
>>> sio = StringIO("new text")
>>> sio.filename = "stuff.txt"
>>> sio.headers = {"Content-Type": "text/plain; charset=utf-8",
... "Content-Disposition": 'attachment; filename="stuff.txt"'}
>>> print http("""
... POST /plain.txt/@@edit.html HTTP/1.1
... Authorization: Basic mgr:mgrpw
... """, form={"form.data": sio,
... "form.actions.edit": "Edit"}, handle_errors=False)
HTTP/1.1 200 ...
Now, let's request the download view of the file object and check the
result:
>>> print http("""
... GET /plain.txt/@@download HTTP/1.1
... Authorization: Basic mgr:mgrpw
... """, handle_errors=False)
HTTP/1.1 200 Ok
Content-Disposition: attachment; filename="plain.txt"
Content-Length: 8
Content-Type: text/plain;charset=utf-8
new text
If we upload a file that has imprecise content type information (as we
expect from browsers generally, and MSIE most significantly), we can
see that the MIME type machinery will improve the information where
possible:
>>> sio = StringIO("\n"
... "...\n")
>>> sio.filename = "simple.html"
>>> sio.headers = {
... "Content-Type": "text/html; charset=utf-8",
... "Content-Disposition": 'attachment; filename="simple.html"',
... }
>>> print http("""
... POST /@@+/zope.file.File HTTP/1.1
... Authorization: Basic mgr:mgrpw
... """, form={"form.data": sio,
... "form.actions.add": "Add"}, handle_errors=False)
HTTP/1.1 303 ...
Again, we'll request the download view of the file object and check
the result:
>>> print http("""
... GET /simple.html/@@download HTTP/1.1
... Authorization: Basic mgr:mgrpw
... """, handle_errors=False)
HTTP/1.1 200 Ok
Content-Disposition: attachment; filename="simple.html"
Content-Length: 56
Content-Type: application/xhtml+xml;charset=utf-8
...
Further, if a browser is bad and sends a full path as the file name (as
sometimes happens in many browsers, apparently), the name is correctly
truncated and changed.
>>> sio = StringIO("\n"
... "...\n")
>>> sio.filename = r"C:\Documents and Settings\Joe\naughty name.html"
>>> sio.headers = {
... "Content-Type": "text/html; charset=utf-8",
... "Content-Disposition": 'attachment; filename=%s' % sio.filename,
... }
>>> print http("""
... POST /@@+/zope.file.File HTTP/1.1
... Authorization: Basic mgr:mgrpw
... """, form={"form.data": sio,
... "form.actions.add": "Add"}, handle_errors=False)
HTTP/1.1 303 ...
Again, we'll request the download view of the file object and check
the result:
>>> print http("""
... GET /naughty%20name.html/@@download HTTP/1.1
... Authorization: Basic mgr:mgrpw
... """, handle_errors=False)
HTTP/1.1 200 Ok
Content-Disposition: attachment; filename="naughty name.html"
Content-Length: 56
Content-Type: application/xhtml+xml;charset=utf-8
...
In zope.file <= 0.5.0, a redundant ObjectCreatedEvent was fired in the
Upload view. We'll demonstrate that this is no longer the case.
>>> import zope.component
>>> from zope.file.interfaces import IFile
>>> from zope.lifecycleevent import IObjectCreatedEvent
We'll register a subscriber for IObjectCreatedEvent that simply increments
a counter.
>>> count = 0
>>> def inc(*args):
... global count; count += 1
>>> zope.component.provideHandler(inc, (IFile, IObjectCreatedEvent))
>>> print http("""
... POST /@@+/zope.file.File HTTP/1.1
... Authorization: Basic mgr:mgrpw
... """, form={"form.data": sio,
... "form.actions.add": "Add"}, handle_errors=False)
HTTP/1.1 303 ...
The subscriber was called only once.
>>> print count
1
==================================
Content type and encoding controls
==================================
Files provide a view that supports controlling the MIME content type
and, where applicable, the content encoding. Content encoding is
applicable based on the specific content type of the file.
Let's demonstrate the behavior of the form with a simple bit of
content. We'll upload a bit of HTML as a sample document:
>>> import StringIO
>>> sio = StringIO.StringIO("A little HTML."
... " There's one 8-bit Latin-1 character: \xd8.")
>>> from zope.testbrowser.testing import Browser
>>> browser = Browser()
>>> browser.addHeader("Authorization", "Basic mgr:mgrpw")
>>> browser.addHeader("Accept-Language", "en-US")
>>> browser.open("http://localhost/@@+/zope.file.File")
>>> ctrl = browser.getControl(name="form.data")
>>> ctrl.mech_control.add_file(
... sio, "text/html", "sample.html")
>>> browser.getControl("Add").click()
We can see that the MIME handlers have marked this as HTML content:
>>> import zope.mimetype.interfaces
>>> import zope.mimetype.types
>>> file = getRootFolder()["sample.html"]
>>> zope.mimetype.types.IContentTypeTextHtml.providedBy(file)
True
It's important to note that this also means the content is encoded
text:
>>> zope.mimetype.interfaces.IContentTypeEncoded.providedBy(file)
True
The "Content Type" page will show us the MIME type and encoding that
have been selected:
>>> browser.getLink("sample.html").click()
>>> browser.getLink("Content Type").click()
>>> browser.getControl(name="form.mimeType").value
['zope.mimetype.types.IContentTypeTextHtml']
The empty string value indicates that we have no encoding
information:
>>> ctrl = browser.getControl(name="form.encoding")
>>> print ctrl.value
['']
Let's now set the encoding value to an old favorite, Latin-1:
>>> ctrl.value = ["iso-8859-1"]
>>> browser.handleErrors = False
>>> browser.getControl("Save").click()
We now see the updated value in the form, and can check the value in
the MIME content-type parameters on the object:
>>> ctrl = browser.getControl(name="form.encoding")
>>> print ctrl.value
['iso-8859-1']
>>> file = getRootFolder()["sample.html"]
>>> file.parameters
{'charset': 'iso-8859-1'}
Something more interesting is that we can now use a non-encoded
content type, and the encoding field will be removed from the form:
>>> ctrl = browser.getControl(name="form.mimeType")
>>> ctrl.value = ["zope.mimetype.types.IContentTypeImageTiff"]
>>> browser.getControl("Save").click()
>>> browser.getControl(name="form.encoding")
Traceback (most recent call last):
...
LookupError: name 'form.encoding'
If we switch back to an encoded type, we see that our encoding wasn't
lost:
>>> ctrl = browser.getControl(name="form.mimeType")
>>> ctrl.value = ["zope.mimetype.types.IContentTypeTextHtml"]
>>> browser.getControl("Save").click()
>>> browser.getControl(name="form.encoding").value
['iso-8859-1']
On the other hand, if we try setting the encoding to something which
simply cannot decode the input data, we get an error message saying
that's not going to work, and no changes are saved:
>>> ctrl = browser.getControl(name="form.encoding")
>>> ctrl.value = ["utf-8"]
>>> browser.getControl("Save").click()
>>> print browser.contents
<...Selected encoding cannot decode document...
=====================
Presentation Adapters
=====================
Object size
-----------
The size of the file as presented in the contents view of a container is
provided using an adapter implementing the `zope.size.interfaces.ISized`
interface. Such an adapter is available for the file object.
Let's do some imports and create a new file object:
>>> from zope.file.file import File
>>> from zope.file.browser import Sized
>>> from zope.size.interfaces import ISized
>>> f = File()
>>> f.size
0
>>> s = Sized(f)
>>> ISized.providedBy(s)
True
>>> s.sizeForSorting()
('byte', 0)
>>> s.sizeForDisplay()
u'0 KB'
Let's add some content to the file:
>>> w = f.open('w')
>>> w.write("some text")
>>> w.close()
The sized adapter now reflects the updated size:
>>> s.sizeForSorting()
('byte', 9)
>>> s.sizeForDisplay()
u'1 KB'
Let's try again with a larger file size:
>>> w = f.open('w')
>>> w.write("x" * (1024*1024+10))
>>> w.close()
>>> s.sizeForSorting()
('byte', 1048586)
>>> m = s.sizeForDisplay()
>>> m
u'${size} MB'
>>> m.mapping
{'size': '1.00'}
And still a bigger size:
>>> w = f.open('w')
>>> w.write("x" * 3*512*1024)
>>> w.close()
>>> s.sizeForSorting()
('byte', 1572864)
>>> m = s.sizeForDisplay()
>>> m
u'${size} MB'
>>> m.mapping
{'size': '1.50'}
=======
CHANGES
=======
0.6.2 (2012-06-04)
------------------
- Moved menu-oriented registrations into new menus.zcml. This is now
loaded if zope.app.zcmlfiles is available only.
- Increase test coverage.
0.6.1 (2012-01-26)
------------------
- Declared more dependencies.
0.6.0 (2010-09-16)
------------------
- Bug fix: remove duplicate firing of ObjectCreatedEvent in
zope.file.upload.Upload (the event is already fired in its base class,
zope.formlib.form.AddForm).
- Move browser-related zcml to `browser.zcml` so that it easier for
applications to exclude it.
- Import content-type parser from zope.contenttype, adding a dependency on
that package.
- Removed undeclared dependency on zope.app.container, depend on zope.browser.
- Using Python's ``doctest`` module instead of deprecated
``zope.testing.doctest``.
0.5.0 (2009-07-23)
------------------
- Change package's mailing list address to zope-dev at zope.org instead
of the retired one.
- Made tests compatible with ZODB 3.9.
- Removed not needed install requirement declarations.
0.4.0 (2009-01-31)
------------------
- `openDetached` is now protected by zope.View instead of zope.ManageContent.
- Use zope.container instead of zope.app.container.
0.3.0 (2007-11-01)
------------------
- Package data update.
0.2.0 (2007-04-18)
------------------
- Fix code for Publisher version 3.4.
0.1.0 (2007-04-18)
------------------
- Initial release.
Keywords: zope3 web html ui file pattern
Platform: UNKNOWN
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Web Environment
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Zope Public License
Classifier: Programming Language :: Python
Classifier: Natural Language :: English
Classifier: Operating System :: OS Independent
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Framework :: Zope3
zope.file-0.6.2/setup.py 0000664 0001750 0001750 00000006564 11763154432 014553 0 ustar uli uli 0000000 0000000 ##############################################################################
#
# Copyright (c) 2006-2009 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Setup for zope.file package
$Id: setup.py 80818 2007-10-11 04:06:12Z srichter $
"""
import os
from setuptools import setup, find_packages
def read(*rnames):
return open(os.path.join(os.path.dirname(__file__), *rnames)).read()
setup(name='zope.file',
version='0.6.2',
author='Zope Corporation and Contributors',
author_email='zope-dev@zope.org',
description='Efficient File Implementation for Zope Applications',
long_description=(
read('README.txt')
+ '\n\n' +
'.. contents::'
+ '\n\n' +
read('src', 'zope', 'file', 'README.txt')
+ '\n\n' +
read('src', 'zope', 'file', 'download.txt')
+ '\n\n' +
read('src', 'zope', 'file', 'upload.txt')
+ '\n\n' +
read('src', 'zope', 'file', 'contenttype.txt')
+ '\n\n' +
read('src', 'zope', 'file', 'browser.txt')
+ '\n\n' +
read('CHANGES.txt')
),
keywords = "zope3 web html ui file pattern",
classifiers = [
'Development Status :: 5 - Production/Stable',
'Environment :: Web Environment',
'Intended Audience :: Developers',
'License :: OSI Approved :: Zope Public License',
'Programming Language :: Python',
'Natural Language :: English',
'Operating System :: OS Independent',
'Topic :: Internet :: WWW/HTTP',
'Framework :: Zope3'],
url='http://cheeseshop.python.org/pypi/zope.file',
license='ZPL 2.1',
packages=find_packages('src'),
package_dir = {'': 'src'},
namespace_packages=['zope'],
extras_require = dict(
test=['zope.app.component',
'zope.app.server',
'zope.app.testing',
'zope.app.zcmlfiles',
'zope.login',
'zope.password',
'zope.securitypolicy',
'zope.testbrowser']),
install_requires=['setuptools',
'ZODB3',
'zope.component',
'zope.browser',
'zope.container',
'zope.contenttype>=3.5',
'zope.event',
'zope.filerepresentation',
'zope.formlib>=4',
'zope.i18nmessageid',
'zope.lifecycleevent',
'zope.interface',
'zope.location',
'zope.mimetype',
'zope.publisher',
'zope.schema',
'zope.security',
'zope.size',
],
include_package_data = True,
zip_safe = False,
)
zope.file-0.6.2/README.txt 0000664 0001750 0001750 00000000176 11763153771 014535 0 ustar uli uli 0000000 0000000 The `zope.file` package provides a content object used to store a
file. The interface supports efficient upload and download.
zope.file-0.6.2/ZopePublicLicense.txt 0000664 0001750 0001750 00000004203 11763153771 017152 0 ustar uli uli 0000000 0000000 Zope Public License (ZPL) Version 2.1
-------------------------------------
A copyright notice accompanies this license document that
identifies the copyright holders.
This license has been certified as open source. It has also
been designated as GPL compatible by the Free Software
Foundation (FSF).
Redistribution and use in source and binary forms, with or
without modification, are permitted provided that the
following conditions are met:
1. Redistributions in source code must retain the
accompanying copyright notice, this list of conditions,
and the following disclaimer.
2. Redistributions in binary form must reproduce the accompanying
copyright notice, this list of conditions, and the
following disclaimer in the documentation and/or other
materials provided with the distribution.
3. Names of the copyright holders must not be used to
endorse or promote products derived from this software
without prior written permission from the copyright
holders.
4. The right to distribute this software or to use it for
any purpose does not give you the right to use
Servicemarks (sm) or Trademarks (tm) of the copyright
holders. Use of them is covered by separate agreement
with the copyright holders.
5. If any files are modified, you must cause the modified
files to carry prominent notices stating that you changed
the files and the date of any change.
Disclaimer
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS''
AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
NO EVENT SHALL THE COPYRIGHT HOLDERS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
DAMAGE.
zope.file-0.6.2/buildout.cfg 0000664 0001750 0001750 00000000646 11763153771 015351 0 ustar uli uli 0000000 0000000 [buildout]
develop = .
parts = test coverage-test coverage-report
[test]
recipe = zc.recipe.testrunner
eggs = zope.file [test]
[coverage-test]
recipe = zc.recipe.testrunner
eggs = zope.file [test]
defaults = ['--tests-pattern', '^f?tests$', '-v', '--coverage', '../../coverage']
[coverage-report]
recipe = zc.recipe.egg
eggs = z3c.coverage
scripts = coverage=coverage-report
arguments = ('coverage', 'coverage/report')
zope.file-0.6.2/bootstrap.py 0000664 0001750 0001750 00000003367 11763153771 015433 0 ustar uli uli 0000000 0000000 ##############################################################################
#
# Copyright (c) 2006 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Bootstrap a buildout-based project
Simply run this script in a directory containing a buildout.cfg.
The script accepts buildout command-line options, so you can
use the -c option to specify an alternate configuration file.
$Id: bootstrap.py 69908 2006-08-31 21:53:00Z jim $
"""
import os, shutil, sys, tempfile, urllib2
tmpeggs = tempfile.mkdtemp()
ez = {}
exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py'
).read() in ez
ez['use_setuptools'](to_dir=tmpeggs, download_delay=0)
import pkg_resources
cmd = 'from setuptools.command.easy_install import main; main()'
if sys.platform == 'win32':
cmd = '"%s"' % cmd # work around spawn lamosity on windows
ws = pkg_resources.working_set
assert os.spawnle(
os.P_WAIT, sys.executable, sys.executable,
'-c', cmd, '-mqNxd', tmpeggs, 'zc.buildout',
dict(os.environ,
PYTHONPATH=
ws.find(pkg_resources.Requirement.parse('setuptools')).location
),
) == 0
ws.add_entry(tmpeggs)
ws.require('zc.buildout')
import zc.buildout.buildout
zc.buildout.buildout.main(sys.argv[1:] + ['bootstrap'])
shutil.rmtree(tmpeggs)
zope.file-0.6.2/CHANGES.txt 0000664 0001750 0001750 00000002644 11763153771 014652 0 ustar uli uli 0000000 0000000 =======
CHANGES
=======
0.6.2 (2012-06-04)
------------------
- Moved menu-oriented registrations into new menus.zcml. This is now
loaded if zope.app.zcmlfiles is available only.
- Increase test coverage.
0.6.1 (2012-01-26)
------------------
- Declared more dependencies.
0.6.0 (2010-09-16)
------------------
- Bug fix: remove duplicate firing of ObjectCreatedEvent in
zope.file.upload.Upload (the event is already fired in its base class,
zope.formlib.form.AddForm).
- Move browser-related zcml to `browser.zcml` so that it easier for
applications to exclude it.
- Import content-type parser from zope.contenttype, adding a dependency on
that package.
- Removed undeclared dependency on zope.app.container, depend on zope.browser.
- Using Python's ``doctest`` module instead of deprecated
``zope.testing.doctest``.
0.5.0 (2009-07-23)
------------------
- Change package's mailing list address to zope-dev at zope.org instead
of the retired one.
- Made tests compatible with ZODB 3.9.
- Removed not needed install requirement declarations.
0.4.0 (2009-01-31)
------------------
- `openDetached` is now protected by zope.View instead of zope.ManageContent.
- Use zope.container instead of zope.app.container.
0.3.0 (2007-11-01)
------------------
- Package data update.
0.2.0 (2007-04-18)
------------------
- Fix code for Publisher version 3.4.
0.1.0 (2007-04-18)
------------------
- Initial release.