diff --git a/CHANGES.rst b/CHANGES.rst
index 26bc3d8..b81a3f7 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -7,6 +7,9 @@
 
 - Drop support for Python 3.4.
 
+- ``FileUpload`` objects now support the ``seekable()`` method on Python 3.
+  Fixes `issue 44 <https://github.com/zopefoundation/zope.publisher/issue/44>`_.
+
 
 5.0.1 (2018-10-19)
 ==================
diff --git a/src/zope/publisher/browser.py b/src/zope/publisher/browser.py
index 001014c..ca10d3f 100644
--- a/src/zope/publisher/browser.py
+++ b/src/zope/publisher/browser.py
@@ -641,7 +641,7 @@ class FileUpload(object):
             methods = ['close', 'fileno', 'flush', 'isatty',
                 'read', 'readline', 'readlines', 'seek',
                 'tell', 'truncate', 'write', 'writelines',
-                'name']
+                'name', 'seekable']
 
         d = self.__dict__
         for m in methods:

AttributeError: 'FileUpload' object has no attribute 'seekable'
If you pass self.request['file_field'] to zipfile.ZipFile() on Python 3.7, you will get this error:

    Traceback (most recent call last):
        ...
      File "/usr/lib/python3.7/zipfile.py", line 1428, in read
        with self.open(name, "r", pwd) as fp:
      File "/usr/lib/python3.7/zipfile.py", line 1480, in open
        self._fpclose, self._lock, lambda: self._writing)
      File "/usr/lib/python3.7/zipfile.py", line 722, in __init__
        self.seekable = file.seekable
    AttributeError: 'FileUpload' object has no attribute 'seekable'

Adding `seekable` to the `methods` list in `FileUpload.__init__` is enough to make the error go away.

This error does not show up on Python 3.5.  I haven't checked Python 3.6 yet.
diff --git a/src/zope/publisher/tests/test_browserrequest.py b/src/zope/publisher/tests/test_browserrequest.py
index f033737..07533fb 100644
--- a/src/zope/publisher/tests/test_browserrequest.py
+++ b/src/zope/publisher/tests/test_browserrequest.py
@@ -227,6 +227,10 @@ class BrowserTests(HTTPTests):
         # Test that we can actually read the file data
         self.assertEqual(request.form['upload'].read(), b'Some data')
 
+        if not PYTHON2:
+            # File objects on Python 3 have a seekable() method
+            self.assertTrue(request.form['upload'].seekable())
+
     def testLargePostValue(self):
         extra = {'REQUEST_METHOD':'POST',
                  'PATH_INFO': u"/",

pip install -e .[test]
parse_log_pytest
pytest
pytest
3.9
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
exceptiongroup @ file:///croot/exceptiongroup_1706031385326/work
iniconfig @ file:///home/linux1/recipes/ci/iniconfig_1610983019677/work
packaging @ file:///croot/packaging_1734472117206/work
pluggy @ file:///croot/pluggy_1733169602837/work
pytest @ file:///croot/pytest_1738938843180/work
python-gettext==5.0
pytz==2025.2
six==1.17.0
tomli @ file:///opt/conda/conda-bld/tomli_1657175507142/work
zope.browser==3.1
zope.component==6.0
zope.configuration==6.0
zope.contenttype==5.2
zope.deprecation==5.1
zope.event==5.0
zope.exceptions==5.2
zope.hookable==7.0
zope.i18n==5.2
zope.i18nmessageid==7.0
zope.interface==7.2
zope.location==5.1
zope.proxy==6.1
-e git+https://github.com/zopefoundation/zope.publisher.git@02e5f31a8e1950c5bd24f5a60d77025dd06bb330#egg=zope.publisher
zope.schema==7.0.1
zope.security==7.3
zope.testing==5.1
zope.testrunner==7.2

name: zope.publisher
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - exceptiongroup=1.2.0=py39h06a4308_0
  - iniconfig=1.1.1=pyhd3eb1b0_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - packaging=24.2=py39h06a4308_0
  - pip=25.0=py39h06a4308_0
  - pluggy=1.5.0=py39h06a4308_0
  - pytest=8.3.4=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tomli=2.0.1=py39h06a4308_0
  - tzdata=2025a=h04d1e81_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - python-gettext==5.0
      - pytz==2025.2
      - six==1.17.0
      - zope-browser==3.1
      - zope-component==6.0
      - zope-configuration==6.0
      - zope-contenttype==5.2
      - zope-deprecation==5.1
      - zope-event==5.0
      - zope-exceptions==5.2
      - zope-hookable==7.0
      - zope-i18n==5.2
      - zope-i18nmessageid==7.0
      - zope-interface==7.2
      - zope-location==5.1
      - zope-proxy==6.1
      - zope-schema==7.0.1
      - zope-security==7.3
      - zope-testing==5.1
      - zope-testrunner==7.2
prefix: /opt/conda/envs/zope.publisher

src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testFileUploadPost
src/zope/publisher/tests/test_browserrequest.py::HTTPTests::testBasicAuth src/zope/publisher/tests/test_browserrequest.py::HTTPTests::testCookieErrorToLog src/zope/publisher/tests/test_browserrequest.py::HTTPTests::testCookies src/zope/publisher/tests/test_browserrequest.py::HTTPTests::testCookiesUnicode src/zope/publisher/tests/test_browserrequest.py::HTTPTests::testDeduceServerURL src/zope/publisher/tests/test_browserrequest.py::HTTPTests::testHeaders src/zope/publisher/tests/test_browserrequest.py::HTTPTests::testInterface src/zope/publisher/tests/test_browserrequest.py::HTTPTests::testRedirect src/zope/publisher/tests/test_browserrequest.py::HTTPTests::testRequestEnvironment src/zope/publisher/tests/test_browserrequest.py::HTTPTests::testRequestLocale src/zope/publisher/tests/test_browserrequest.py::HTTPTests::testResponseWriteFaile src/zope/publisher/tests/test_browserrequest.py::HTTPTests::testSetPrincipal src/zope/publisher/tests/test_browserrequest.py::HTTPTests::testTraversalToItem src/zope/publisher/tests/test_browserrequest.py::HTTPTests::testUnicodeURLs src/zope/publisher/tests/test_browserrequest.py::HTTPTests::testUnregisteredStatus src/zope/publisher/tests/test_browserrequest.py::HTTPTests::testUntrustedRedirect src/zope/publisher/tests/test_browserrequest.py::HTTPTests::test_PathTrailingWhitespace src/zope/publisher/tests/test_browserrequest.py::HTTPTests::test_double_dots src/zope/publisher/tests/test_browserrequest.py::HTTPTests::test_getVirtualHostRoot src/zope/publisher/tests/test_browserrequest.py::HTTPTests::test_method src/zope/publisher/tests/test_browserrequest.py::HTTPTests::test_repr src/zope/publisher/tests/test_browserrequest.py::HTTPTests::test_setApplicationNames src/zope/publisher/tests/test_browserrequest.py::HTTPTests::test_setApplicationServer src/zope/publisher/tests/test_browserrequest.py::HTTPTests::test_setVirtualHostRoot src/zope/publisher/tests/test_browserrequest.py::HTTPTests::test_traverse src/zope/publisher/tests/test_browserrequest.py::HTTPTests::test_traverseDuplicateHooks src/zope/publisher/tests/test_browserrequest.py::HTTPTests::test_unacceptable_charset src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testBadPath src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testBadPath2 src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testBasicAuth src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testCookieErrorToLog src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testCookies src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testCookiesUnicode src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testDeduceServerURL src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testDefault src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testDefault2 src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testDefault3 src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testDefault4 src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testDefault6 src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testDefaultPOST src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testForm src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testFormAcceptsStarButNotUTF8 src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testFormBooleanTypes src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testFormDefaults src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testFormDefaults2 src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testFormFieldName src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testFormFieldValue src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testFormFloatTypes src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testFormIntTypes src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testFormLinesTypes src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testFormListRecordTypes src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testFormListTypes src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testFormListTypes2 src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testFormLongTypes src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testFormMultipleRecordsTypes src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testFormNoEncodingUsesUTF8 src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testFormRecordsTypes src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testFormRequiredTypes src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testFormStringTypes src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testFormTextTypes src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testFormTokensTypes src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testFormTupleRecordTypes src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testFormTupleTypes src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testHeaders src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testInterface src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testIssue394 src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testIssue559 src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testLargePostValue src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testNoDefault src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testNoneFieldNamePost src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testQueryStringIgnoredForPOST src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testRedirect src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testRequestEnvironment src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testRequestLocale src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testResponseWriteFaile src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testSetPrincipal src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testTraversalToItem src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testUnicodeURLs src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testUnregisteredStatus src/zope/publisher/tests/test_browserrequest.py::BrowserTests::testUntrustedRedirect src/zope/publisher/tests/test_browserrequest.py::BrowserTests::test_PathTrailingWhitespace src/zope/publisher/tests/test_browserrequest.py::BrowserTests::test_double_dots src/zope/publisher/tests/test_browserrequest.py::BrowserTests::test_getVirtualHostRoot src/zope/publisher/tests/test_browserrequest.py::BrowserTests::test_method src/zope/publisher/tests/test_browserrequest.py::BrowserTests::test_post_body_not_consumed_unnecessarily src/zope/publisher/tests/test_browserrequest.py::BrowserTests::test_post_body_not_necessarily src/zope/publisher/tests/test_browserrequest.py::BrowserTests::test_repr src/zope/publisher/tests/test_browserrequest.py::BrowserTests::test_setApplicationNames src/zope/publisher/tests/test_browserrequest.py::BrowserTests::test_setApplicationServer src/zope/publisher/tests/test_browserrequest.py::BrowserTests::test_setVirtualHostRoot src/zope/publisher/tests/test_browserrequest.py::BrowserTests::test_traverse src/zope/publisher/tests/test_browserrequest.py::BrowserTests::test_traverseDuplicateHooks src/zope/publisher/tests/test_browserrequest.py::BrowserTests::test_unacceptable_charset src/zope/publisher/tests/test_browserrequest.py::APITests::testEnvironment src/zope/publisher/tests/test_browserrequest.py::APITests::testGetAndDefaultInMapping src/zope/publisher/tests/test_browserrequest.py::APITests::testHaveCustomTestsForIApplicationRequest src/zope/publisher/tests/test_browserrequest.py::APITests::testHaveCustomTestsForIPublicationRequest src/zope/publisher/tests/test_browserrequest.py::APITests::testHaveCustomTestsForIPublisherRequest src/zope/publisher/tests/test_browserrequest.py::APITests::testHoldCloseAndGetResponse src/zope/publisher/tests/test_browserrequest.py::APITests::testIReadMapping src/zope/publisher/tests/test_browserrequest.py::APITests::testPublicationManagement src/zope/publisher/tests/test_browserrequest.py::APITests::testSkinManagement src/zope/publisher/tests/test_browserrequest.py::APITests::testTraversalStack src/zope/publisher/tests/test_browserrequest.py::APITests::testVerifyIApplicationRequest src/zope/publisher/tests/test_browserrequest.py::APITests::testVerifyIPublicationRequest src/zope/publisher/tests/test_browserrequest.py::APITests::testVerifyIPublisherRequest src/zope/publisher/tests/test_browserrequest.py::APITests::testVerifyISkinnable src/zope/publisher/tests/test_browserrequest.py::APITests::test_IApplicationRequest_bodyStream src/zope/publisher/tests/test_browserrequest.py::APITests::test_IBrowserRequest src/zope/publisher/tests/test_browserrequest.py::APITests::test_IPublicationRequest_getPositionalArguments src/zope/publisher/tests/test_browserrequest.py::APITests::test_IPublisherRequest_processInputs src/zope/publisher/tests/test_browserrequest.py::APITests::test_IPublisherRequest_retry src/zope/publisher/tests/test_browserrequest.py::APITests::test_IPublisherRequest_traverse src/zope/publisher/tests/test_browserrequest.py::APITests::test_ISkinnable src/zope/publisher/tests/test_browserrequest.py::APITests::test___len__ src/zope/publisher/tests/test_browserrequest.py::APITests::test_items src/zope/publisher/tests/test_browserrequest.py::APITests::test_keys src/zope/publisher/tests/test_browserrequest.py::APITests::test_values src/zope/publisher/tests/test_browserrequest.py::test_suite
src/zope/publisher/browser.py CHANGES.rst
src/zope/publisher/browser.py CHANGES.rst