diff --git a/.coveragerc b/.coveragerc
deleted file mode 100644
index 306ad7c..0000000
--- a/.coveragerc
+++ /dev/null
@@ -1,6 +0,0 @@
-[run]
-branch = True
-source = Products.PythonScripts
-
-[report]
-precision = 2
diff --git a/.travis.yml b/.travis.yml
index 040b855..0185c0d 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -4,10 +4,6 @@ matrix:
     include:
         - python: "3.6"
           env: TOXENV=lint
-        - python: "3.6"
-          env: TOXENV=coverage
-          after_success:
-              - coveralls
         - python: "2.7"
           env: TOXENV=py27
         - python: "3.5"
@@ -16,18 +12,20 @@ matrix:
           env: TOXENV=py36
         - python: "3.7"
           env: TOXENV=py37
-          dist: xenial
         - python: "3.8"
           env: TOXENV=py38
-          dist: xenial
 
 install:
-    - travis_retry pip install -U pip setuptools zc.buildout
+    - travis_retry pip install -U pip setuptools
     - travis_retry pip install -U tox coveralls coverage
 
 script:
     - travis_retry tox
 
+after_success:
+    - coverage combine
+    - coveralls
+
 notifications:
     email: false
 
diff --git a/CHANGES.rst b/CHANGES.rst
index 425ed06..ec7d159 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -3,6 +3,8 @@ Changelog
 
 4.12 (unreleased)
 -----------------
+- fix ``TypeError`` when updating an existing script from a file
+  (`#43 <https://github.com/zopefoundation/Products.PythonScripts/issues/43>`_)
 
 
 4.11 (2020-02-11)
diff --git a/buildout.cfg b/buildout.cfg
index cdb43d4..6a4645a 100644
--- a/buildout.cfg
+++ b/buildout.cfg
@@ -4,15 +4,9 @@ extends =
 develop = .
 parts =
     test
-    scripts
 
 [test]
 recipe = zc.recipe.testrunner
 defaults = ['-cv']
 eggs =
     Products.PythonScripts
-
-[scripts]
-recipe = zc.recipe.egg
-eggs =
-    tox
diff --git a/setup.cfg b/setup.cfg
index 32edae8..74cab64 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -29,11 +29,10 @@ htmldir = parts/flake8
 
 [coverage:run]
 branch = True
-source = Products
+source = Products.PythonScripts
 omit =
 
 [coverage:report]
-fail_under = 85.00
 ignore_errors = True
 precision = 2
 show_missing = False
diff --git a/src/Products/PythonScripts/PythonScript.py b/src/Products/PythonScripts/PythonScript.py
index 0f241b3..739d17c 100644
--- a/src/Products/PythonScripts/PythonScript.py
+++ b/src/Products/PythonScripts/PythonScript.py
@@ -48,6 +48,7 @@ from Shared.DC.Scripts.Script import BindingsUI
 from Shared.DC.Scripts.Script import Script
 from Shared.DC.Scripts.Script import defaultBindings
 from zExceptions import Forbidden
+from ZPublisher.HTTPRequest import default_encoding
 
 
 try:
@@ -416,10 +417,6 @@ class PythonScript(Script, Historical, Cacheable):
         self.dav__init(REQUEST, RESPONSE)
         self.dav__simpleifhandler(REQUEST, RESPONSE, refresh=1)
         new_body = REQUEST.get('BODY', '')
-        if six.PY3 and isinstance(new_body, bytes):
-            new_body = new_body.decode('UTF-8')
-        elif six.PY2 and not isinstance(new_body, bytes):
-            new_body = new_body.encode('UTF-8')
         self.write(new_body)
         RESPONSE.setStatus(204)
         return RESPONSE
@@ -434,6 +431,11 @@ class PythonScript(Script, Historical, Cacheable):
         bindmap = self.getBindingAssignments().getAssignedNames()
         bup = 0
 
+        if six.PY3 and isinstance(text, bytes):
+            text = text.decode(default_encoding)
+        elif six.PY2 and not isinstance(text, bytes):
+            text = text.encode(default_encoding)
+
         st = 0
         try:
             while 1:
diff --git a/tox.ini b/tox.ini
index 625dffc..17b4d90 100644
--- a/tox.ini
+++ b/tox.ini
@@ -12,24 +12,30 @@ envlist =
 commands =
     {envbindir}/buildout -c {toxinidir}/buildout.cfg buildout:directory={envdir} buildout:develop={toxinidir} bootstrap
     {envbindir}/buildout -c {toxinidir}/buildout.cfg buildout:directory={envdir} buildout:develop={toxinidir} install test
-    {envbindir}/test --all {posargs}
+    coverage run {envbindir}/test --all {posargs}
 deps =
     zc.buildout
+    coverage
 skip_install = true
+setenv =
+    COVERAGE_FILE=.coverage.{envname}
 
 [testenv:coverage]
 basepython = python3.6
+depends =
+    py27,
+    py35,
+    py36,
+    py37,
+    py38,
 commands =
-    {envbindir}/buildout -c {toxinidir}/buildout.cfg buildout:directory={envdir} buildout:develop={toxinidir} bootstrap
-    {envbindir}/buildout -c {toxinidir}/buildout.cfg buildout:directory={envdir} buildout:develop={toxinidir} install test
     coverage erase
-    coverage run {envbindir}/test --all {posargs}
-    coverage html
-    coverage xml
-    coverage report --fail-under=72
+    coverage combine
+    coverage html -i
+    coverage xml -i
+    coverage report -i --fail-under=75
 deps =
     {[testenv]deps}
-    coverage
 setenv =
     COVERAGE_FILE=.coverage
 

Error on Create new script from file
When I create a new Python Script in the ZMI, there is the option of providing a text-file with the code. When I use this option I get an Internal Server Error:

    2020-05-21 10:26:17 ERROR [waitress:356][waitress] Exception while serving /websites/manage_addProduct/PythonScripts/manage_addPythonScript
    Traceback (most recent call last):
      File "/path/to/my/Zope/installation/lib/python3.8/site-packages/waitress/channel.py", line 349, in service
        task.service()
      File "/path/to/my/Zope/installation/lib/python3.8/site-packages/waitress/task.py", line 169, in service
        self.execute()
      File "/path/to/my/Zope/installation/lib/python3.8/site-packages/waitress/task.py", line 439, in execute
        app_iter = self.channel.server.application(environ, start_response)
      File "/path/to/my/Zope/installation/lib/python3.8/site-packages/ZPublisher/httpexceptions.py", line 30, in __call__
        return self.application(environ, start_response)
      File "/path/to/my/Zope/installation/lib/python3.8/site-packages/paste/translogger.py", line 69, in __call__
        return self.application(environ, replacement_start_response)
      File "/path/to/my/Zope/installation/lib/python3.8/site-packages/ZPublisher/WSGIPublisher.py", line 359, in publish_module
        response = _publish(request, new_mod_info)
      File "/path/to/my/Zope/installation/lib/python3.8/site-packages/ZPublisher/WSGIPublisher.py", line 254, in publish
        result = mapply(obj,
      File "/path/to/my/Zope/installation/lib/python3.8/site-packages/ZPublisher/mapply.py", line 85, in mapply
        return debug(object, args, context)
      File "/path/to/my/Zope/installation/lib/python3.8/site-packages/ZPublisher/WSGIPublisher.py", line 63, in call_object
        return obj(*args)
      File "/path/to/my/Zope/installation/lib/python3.8/site-packages/Products/PythonScripts/PythonScript.py", line 97, in manage_addPythonScript
        pyscript.write(file)
      File "/path/to/my/Zope/installation/lib/python3.8/site-packages/Products/PythonScripts/PythonScript.py", line 441, in write
        m = _nonempty_line.search(text, st)
    TypeError: cannot use a string pattern on a bytes-like object

diff --git a/src/Products/PythonScripts/tests/testPythonScript.py b/src/Products/PythonScripts/tests/testPythonScript.py
index d91ef47..3819149 100644
--- a/src/Products/PythonScripts/tests/testPythonScript.py
+++ b/src/Products/PythonScripts/tests/testPythonScript.py
@@ -295,6 +295,15 @@ return \xc3\xa4\xc3\xa9\xc3\xae\xc3\xb6\xc3\xbc
                 'return \xc3\xa4\xc3\xa9\xc3\xae\xc3\xb6\xc3\xbc\n')
         self.assertEqual(ps.params(), 'oops')
 
+    def test_write(self):
+        ps = self._newPS('')
+
+        ps.write(b'return 1')
+        self.assertEqual(ps.body(), 'return 1\n')
+
+        ps.write(u'return 1')
+        self.assertEqual(ps.body(), 'return 1\n')
+
 
 class TestPythonScriptErrors(PythonScriptTestBase):
 
@@ -430,3 +439,11 @@ class PythonScriptBrowserTests(FunctionalTestCase):
         """It renders an error message if no file is uploaded."""
         self.browser.getControl('Upload File').click()
         self.assertIn('No file specified', self.browser.contents)
+
+    def test_ZPythonScriptHTML_upload__with_file(self):
+        file_contents = b'print("hello")'
+        self.browser.getControl('file').add_file(
+             file_contents, 'text/plain', 'script.py')
+        self.browser.getControl('Upload File').click()
+
+        assert 'Saved changes.' in self.browser.contents

pip install -e .
parse_log_pytest
requirements.txt
pytest pytest-cov
apt-get update apt-get install -y gcc
3.6
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
AccessControl==5.7
Acquisition==4.13
attrs==22.2.0
AuthEncoding==4.3
beautifulsoup4==4.12.3
BTrees==4.11.3
certifi==2021.5.30
cffi==1.15.1
Chameleon==3.10.2
coverage==6.2
DateTime==4.9
DocumentTemplate==4.3
ExtensionClass==4.9
importlib-metadata==4.8.3
iniconfig==1.1.1
MultiMapping==4.1
multipart==1.1.0
packaging==21.3
PasteDeploy==2.1.1
Persistence==3.6
persistent==4.9.3
pluggy==1.0.0
-e git+https://github.com/zopefoundation/Products.PythonScripts.git@e4f9240f68ddcfaf680ae88fe874176cb6b1d294#egg=Products.PythonScripts
py==1.11.0
pycparser==2.21
pyparsing==3.1.4
pytest==7.0.1
pytest-cov==4.0.0
python-gettext==4.1
pytz==2025.2
RestrictedPython==6.2
roman==3.3
six==1.17.0
soupsieve==2.3.2.post1
tomli==1.2.3
transaction==3.1.0
typing_extensions==4.1.1
waitress==2.0.0
WebOb==1.8.9
WebTest==3.0.0
WSGIProxy2==0.5.1
z3c.pt==3.3.1
zc.lockfile==2.0
ZConfig==3.6.1
zExceptions==4.3
zipp==3.6.0
ZODB==5.8.1
zodbpickle==2.6
Zope==5.7.3
zope.annotation==4.8
zope.browser==2.4
zope.browsermenu==4.4
zope.browserpage==4.4.0
zope.browserresource==4.4
zope.cachedescriptors==4.4
zope.component==5.1.0
zope.configuration==4.4.1
zope.container==4.10
zope.contentprovider==4.2.1
zope.contenttype==4.6
zope.datetime==4.3.0
zope.deferredimport==4.4
zope.deprecation==4.4.0
zope.dottedname==5.0
zope.event==4.6
zope.exceptions==4.6
zope.filerepresentation==5.0.0
zope.globalrequest==1.6
zope.hookable==5.4
zope.i18n==4.9.0
zope.i18nmessageid==5.1.1
zope.interface==5.5.2
zope.lifecycleevent==4.4
zope.location==4.3
zope.pagetemplate==4.6.0
zope.processlifetime==2.4
zope.proxy==4.6.1
zope.ptresource==4.3.0
zope.publisher==6.1.0
zope.schema==6.2.1
zope.security==5.8
zope.sequencesort==4.2
zope.site==4.6.1
zope.size==4.4
zope.structuredtext==4.4
zope.tal==4.5
zope.tales==5.2
zope.testbrowser==5.6.1
zope.testing==5.0.1
zope.traversing==4.4.1
zope.viewlet==4.3

name: Products.PythonScripts
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
  - certifi=2021.5.30=py36h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.3=he6710b0_2
  - 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=1.1.1w=h7f8727e_0
  - pip=21.2.2=py36h06a4308_0
  - python=3.6.13=h12debd9_1
  - readline=8.2=h5eee18b_0
  - setuptools=58.0.4=py36h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.37.1=pyhd3eb1b0_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - accesscontrol==5.7
      - acquisition==4.13
      - attrs==22.2.0
      - authencoding==4.3
      - beautifulsoup4==4.12.3
      - btrees==4.11.3
      - cffi==1.15.1
      - chameleon==3.10.2
      - coverage==6.2
      - datetime==4.9
      - documenttemplate==4.3
      - extensionclass==4.9
      - importlib-metadata==4.8.3
      - iniconfig==1.1.1
      - multimapping==4.1
      - multipart==1.1.0
      - packaging==21.3
      - pastedeploy==2.1.1
      - persistence==3.6
      - persistent==4.9.3
      - pluggy==1.0.0
      - py==1.11.0
      - pycparser==2.21
      - pyparsing==3.1.4
      - pytest==7.0.1
      - pytest-cov==4.0.0
      - python-gettext==4.1
      - pytz==2025.2
      - restrictedpython==6.2
      - roman==3.3
      - six==1.17.0
      - soupsieve==2.3.2.post1
      - tomli==1.2.3
      - transaction==3.1.0
      - typing-extensions==4.1.1
      - waitress==2.0.0
      - webob==1.8.9
      - webtest==3.0.0
      - wsgiproxy2==0.5.1
      - z3c-pt==3.3.1
      - zc-lockfile==2.0
      - zconfig==3.6.1
      - zexceptions==4.3
      - zipp==3.6.0
      - zodb==5.8.1
      - zodbpickle==2.6
      - zope==5.7.3
      - zope-annotation==4.8
      - zope-browser==2.4
      - zope-browsermenu==4.4
      - zope-browserpage==4.4.0
      - zope-browserresource==4.4
      - zope-cachedescriptors==4.4
      - zope-component==5.1.0
      - zope-configuration==4.4.1
      - zope-container==4.10
      - zope-contentprovider==4.2.1
      - zope-contenttype==4.6
      - zope-datetime==4.3.0
      - zope-deferredimport==4.4
      - zope-deprecation==4.4.0
      - zope-dottedname==5.0
      - zope-event==4.6
      - zope-exceptions==4.6
      - zope-filerepresentation==5.0.0
      - zope-globalrequest==1.6
      - zope-hookable==5.4
      - zope-i18n==4.9.0
      - zope-i18nmessageid==5.1.1
      - zope-interface==5.5.2
      - zope-lifecycleevent==4.4
      - zope-location==4.3
      - zope-pagetemplate==4.6.0
      - zope-processlifetime==2.4
      - zope-proxy==4.6.1
      - zope-ptresource==4.3.0
      - zope-publisher==6.1.0
      - zope-schema==6.2.1
      - zope-security==5.8
      - zope-sequencesort==4.2
      - zope-site==4.6.1
      - zope-size==4.4
      - zope-structuredtext==4.4
      - zope-tal==4.5
      - zope-tales==5.2
      - zope-testbrowser==5.6.1
      - zope-testing==5.0.1
      - zope-traversing==4.4.1
      - zope-viewlet==4.3
prefix: /opt/conda/envs/Products.PythonScripts

src/Products/PythonScripts/tests/testPythonScript.py::TestPythonScriptNoAq::test_write
src/Products/PythonScripts/tests/testPythonScript.py::TestPythonScriptGlobals::test_filepath src/Products/PythonScripts/tests/testPythonScript.py::PythonScriptBrowserTests::test_ZPythonScriptHTML_upload__no_file src/Products/PythonScripts/tests/testPythonScript.py::PythonScriptBrowserTests::test_ZPythonScriptHTML_upload__with_file
src/Products/PythonScripts/tests/testPythonScript.py::TestPythonScriptNoAq::testArithmetic src/Products/PythonScripts/tests/testPythonScript.py::TestPythonScriptNoAq::testBigBoolean src/Products/PythonScripts/tests/testPythonScript.py::TestPythonScriptNoAq::testBooleanMap src/Products/PythonScripts/tests/testPythonScript.py::TestPythonScriptNoAq::testBuiltinSet src/Products/PythonScripts/tests/testPythonScript.py::TestPythonScriptNoAq::testComplexPrint src/Products/PythonScripts/tests/testPythonScript.py::TestPythonScriptNoAq::testDateTime src/Products/PythonScripts/tests/testPythonScript.py::TestPythonScriptNoAq::testDoubleNegation src/Products/PythonScripts/tests/testPythonScript.py::TestPythonScriptNoAq::testEmpty src/Products/PythonScripts/tests/testPythonScript.py::TestPythonScriptNoAq::testEncodingTestDotTestAllLaunchpad257276 src/Products/PythonScripts/tests/testPythonScript.py::TestPythonScriptNoAq::testFibonacci src/Products/PythonScripts/tests/testPythonScript.py::TestPythonScriptNoAq::testForLoop src/Products/PythonScripts/tests/testPythonScript.py::TestPythonScriptNoAq::testGetSize src/Products/PythonScripts/tests/testPythonScript.py::TestPythonScriptNoAq::testImport src/Products/PythonScripts/tests/testPythonScript.py::TestPythonScriptNoAq::testIndented src/Products/PythonScripts/tests/testPythonScript.py::TestPythonScriptNoAq::testMutateLiterals src/Products/PythonScripts/tests/testPythonScript.py::TestPythonScriptNoAq::testNSBind src/Products/PythonScripts/tests/testPythonScript.py::TestPythonScriptNoAq::testNSBindInvalidHeader src/Products/PythonScripts/tests/testPythonScript.py::TestPythonScriptNoAq::testParam1 src/Products/PythonScripts/tests/testPythonScript.py::TestPythonScriptNoAq::testParam2 src/Products/PythonScripts/tests/testPythonScript.py::TestPythonScriptNoAq::testParam26 src/Products/PythonScripts/tests/testPythonScript.py::TestPythonScriptNoAq::testRaiseSystemExitLaunchpad257269 src/Products/PythonScripts/tests/testPythonScript.py::TestPythonScriptNoAq::testReduce src/Products/PythonScripts/tests/testPythonScript.py::TestPythonScriptNoAq::testReturn src/Products/PythonScripts/tests/testPythonScript.py::TestPythonScriptNoAq::testReturnNone src/Products/PythonScripts/tests/testPythonScript.py::TestPythonScriptNoAq::testSimplePrint src/Products/PythonScripts/tests/testPythonScript.py::TestPythonScriptNoAq::testTryExcept src/Products/PythonScripts/tests/testPythonScript.py::TestPythonScriptNoAq::testTupleUnpackAssignment src/Products/PythonScripts/tests/testPythonScript.py::TestPythonScriptNoAq::testWhileLoop src/Products/PythonScripts/tests/testPythonScript.py::TestPythonScriptNoAq::test_PUT_bytes src/Products/PythonScripts/tests/testPythonScript.py::TestPythonScriptNoAq::test_PUT_native_string src/Products/PythonScripts/tests/testPythonScript.py::TestPythonScriptNoAq::test_manage_DAVget src/Products/PythonScripts/tests/testPythonScript.py::TestPythonScriptErrors::testAttributeAssignment src/Products/PythonScripts/tests/testPythonScript.py::TestPythonScriptErrors::testBadIdentifiers src/Products/PythonScripts/tests/testPythonScript.py::TestPythonScriptErrors::testBadImports src/Products/PythonScripts/tests/testPythonScript.py::TestPythonScriptErrors::testSubversiveExcept src/Products/PythonScripts/tests/testPythonScript.py::TestPythonScriptGlobals::testGlobalIsDeclaration src/Products/PythonScripts/tests/testPythonScript.py::TestPythonScriptGlobals::test__name__ src/Products/PythonScripts/tests/testPythonScript.py::PythonScriptInterfaceConformanceTests::test_class_conforms_to_IWriteLock
src/Products/PythonScripts/PythonScript.py .travis.yml setup.cfg .coveragerc CHANGES.rst tox.ini buildout.cfg
src/Products/PythonScripts/PythonScript.py .travis.yml setup.cfg .coveragerc CHANGES.rst tox.ini buildout.cfg