Skip to content

Commit

Permalink
Merge branch 'master' into update-zodbpy2topy3-migratio-guide
Browse files Browse the repository at this point in the history
  • Loading branch information
jensens committed May 10, 2019
2 parents 7fd431b + d52b1e8 commit c46fe7c
Show file tree
Hide file tree
Showing 6 changed files with 58 additions and 185 deletions.
15 changes: 9 additions & 6 deletions appveyor.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
# AppVeyor CI settings (Windows Machine CI Tests)

environment:
WIN_COV: ' ' # some whitespaces to have a trueish value.
matrix:
- PROFILE: py37
PYTHON_VERSION: 3.7"
TOXENV: "py37"
- PROFILE: py37-conventions
PYTHON_VERSION: 3.7"
TOXENV: "lint"
Expand All @@ -14,9 +18,6 @@ environment:
- PROFILE: py36
PYTHON_VERSION: 3.6"
TOXENV: "py36"
- PROFILE: py37
PYTHON_VERSION: 3.7"
TOXENV: "py37"

cache:
- '%LOCALAPPDATA%\pip\Cache'
Expand All @@ -28,8 +29,10 @@ install:

build: off

max_jobs: 2

matrix:
fast_finish: true

test_script:
- "tox.exe"

after_test:
- coveralls
187 changes: 18 additions & 169 deletions docs/zdgbook/ObjectPublishing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ Object Publishing
.. note::

Previously, this document contained information about access by
FTP and WebDAV. As those functionalities were provided by the now
removed ZServer, the related information also has been removed.
**FTP** and **WebDAV**. As those functionalities were provided by the
now removed **ZServer**, the related information also has been removed.

Please directly refer to the ZServer package for further
Please directly refer to the **ZServer** package for further
information.


Expand Down Expand Up @@ -1020,7 +1020,7 @@ request constitutes one transaction which Zope takes care of for you.

If an unhandled exception is raised during the publishing process,
Zope aborts the transaction.
When **ConflictErrors** occur, Zope retries the request up to three
When a **ConflictError** occurs, Zope retries the request up to three
times by default. You can change that number in the **zope.conf** by
adding a ``max_conflict_retries`` directive.

Expand All @@ -1033,13 +1033,6 @@ adding a ``max_conflict_retries`` directive.
Manual Access to Request and Response
-------------------------------------

You do not need to access the request and response directly most of
the time. In fact, it is a major design goal of the publisher that
most of the time your objects need not even be aware that they are
being published on the web. However, you have the ability to exert
more precise control over reading the request and returning the
response.

Normally published objects access the request and response by listing
them in the signature of the published method. If this is not
possible you can usually use acquisition to get a reference to the
Expand All @@ -1048,9 +1041,11 @@ from the request like so::

response=REQUEST.RESPONSE

The APIs of the request and response are covered in the API
documentation. Here we'll look at a few common uses of the request
and response.
The APIs of request and response can be looked up in the source code.

We'll look at a few common uses of the request and response. If you
need access to the complete API, please directly refer to the source
code.

One reason to access the request is to get more precise information
about form data. As we mentioned earlier, argument marshalling comes
Expand All @@ -1069,7 +1064,7 @@ control headers::
RESPONSE.setHeader('Pragma', 'No-Cache')

Another reason to access the response is to stream response data. You
can do this with the 'write' method::
can do this with the ``write`` method::

while 1:
data=getMoreData() #this call may block for a while
Expand All @@ -1080,168 +1075,22 @@ can do this with the 'write' method::
Here's a final example that shows how to detect if your method is
being called from the web. Consider this function::

def feedParrot(parrot_id, REQUEST=None):
def calculate(data, REQUEST=None):
...

result = ...
if REQUEST is not None:
return "<html><p>Parrot %s fed</p></html>" % parrot_id
return "<html><p>Result: %s </p></html>" % result
return result

The 'feedParrot' function can be called from Python, and also from the
web. By including 'REQUEST=None' in the signature you can
The ``calculate`` function can be called from Python, and also from
the web. By including ``REQUEST=None`` in the signature you can
differentiate between being called from Python and being called form
the web. When the function is called from Python nothing is returned,
but when it is called from the web the function returns an HTML
confirmation message.
the web.


Other Network Protocols
=======================

FTP
---

Zope comes with an FTP server which allows users to treat the Zope
object hierarchy like a file server. As covered in Chapter 3, Zope
comes with base classes ('SimpleItem' and 'ObjectManager') which
provide simple FTP support for all Zope objects. The FTP API is
covered in the API reference.

To support FTP in your objects you'll need to find a way to represent
your object's state as a file. This is not possible or reasonable for
all types of objects. You should also consider what users will do
with your objects once they access them via FTP. You should find out
which tools users are likely to edit your object files. For example,
XML may provide a good way to represent your object's state, but it
may not be easily editable by your users. Here's an example class
that represents itself as a file using RFC 822 format::

from rfc822 import Message
from cStringIO import StringIO

class Person(...):

def __init__(self, name, email, age):
self.name=name
self.email=email
self.age=age

def writeState(self):
"Returns object state as a string"
return "Name: %s\nEmail: %s\nAge: %s" % (self.name,
self.email,
self.age)
def readState(self, data):
"Sets object state given a string"
m=Message(StringIO(data))
self.name=m['name']
self.email=m['email']
self.age=int(m['age'])

The 'writeState' and 'readState' methods serialize and unserialize the
'name', 'age', and 'email' attributes to and from a string. There are
more efficient ways besides RFC 822 to store instance attributes in a
file, however RFC 822 is a simple format for users to edit with text
editors.

To support FTP all you need to do at this point is implement the
'manage_FTPget' and 'PUT' methods. For example::

def manage_FTPget(self):
"Returns state for FTP"
return self.writeState()

def PUT(self, REQUEST):
"Sets state from FTP"
self.readState(REQUEST['BODY'])

You may also choose to implement a 'get_size' method which returns the
size of the string returned by 'manage_FTPget'. This is only
necessary if calling 'manage_FTPget' is expensive, and there is a more
efficient way to get the size of the file. In the case of this
example, there is no reason to implement a 'get_size' method.

One side effect of implementing 'PUT' is that your object now supports
HTTP PUT publishing. See the next section on WebDAV for more
information on HTTP PUT.

That's all there is to making your object work with FTP. As you'll
see next WebDAV support is similar.

WebDAV
------

WebDAV is a protocol for collaboratively edit and manage files on
remote servers. It provides much the same functionality as FTP, but
it works over HTTP.

It is not difficult to implement WebDAV support for your objects.
Like FTP, the most difficult part is to figure out how to represent
your objects as files.

Your class must inherit from 'webdav.Resource' to get basic DAV
support. However, since 'SimpleItem' inherits from 'Resource', your
class probably already inherits from 'Resource'. For container
classes you must inherit from 'webdav.Collection'. However, since
'ObjectManager' inherits from 'Collection' you are already set so long
as you inherit from 'ObjectManager'.

In addition to inheriting from basic DAV classes, your classes must
implement 'PUT' and 'manage_FTPget'. These two methods are also
required for FTP support. So by implementing WebDAV support, you also
implement FTP support.

The permissions that you assign to these two methods will control the
ability to read and write to your class through WebDAV, but the
ability to see your objects is controlled through the "WebDAV access"
permission.

Supporting Write Locking
------------------------

Write locking is a feature of WebDAV that allows users to put lock on
objects they are working on. Support write locking s easy. To
implement write locking you must assert that your lass implements the
'WriteLockInterface'. For example::

from webdav.WriteLockInterface import WriteLockInterface

class MyContentClass(OFS.SimpleItem.Item, Persistent):
__implements__ = (WriteLockInterface,)

It's sufficient to inherit from 'SimpleItem.Item', since it inherits
from 'webdav.Resource', which provides write locking long with other
DAV support.

In addition, your 'PUT' method should begin with calls to dav__init'
and 'dav_simpleifhandler'. For example::

def PUT(self, REQUEST, RESPONSE):
"""
Implement WebDAV/HTTP PUT/FTP put method for this object.
"""
self.dav__init(REQUEST, RESPONSE)
self.dav__simpleifhandler(REQUEST, RESPONSE)
...

Finally your class's edit methods should check to determine whether
your object is locked using the 'ws_isLocked' method. If someone
attempts to change your object when it is locked you should raise the
'ResourceLockedError'. For example::

from webdav import ResourceLockedError

class MyContentClass(...):
...

def edit(self, ...):
if self.ws_isLocked():
raise ResourceLockedError
...

WebDAV support is not difficult to implement, and as more WebDAV
editors become available, it will become more valuable. If you choose
to add FTP support to your class you should probably go ahead and
support WebDAV too since it is so easy once you've added FTP support.

XML-RPC
-------

Expand Down
23 changes: 19 additions & 4 deletions src/Products/PageTemplates/tests/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,17 @@
import os
import re
import sys
import unittest

import six

from ExtensionClass import Base


# Dummy TestCase to use the assertions outside the actual tests.
TEST_CASE = unittest.TestCase('__init__')


class Bruce(Base):
__allow_access_to_unprotected_subobjects__ = 1
isDocTemp = 0
Expand Down Expand Up @@ -88,13 +95,13 @@ def getPhysicalRoot(self):
def check_html(s1, s2):
s1 = normalize_html(s1)
s2 = normalize_html(s2)
assert s1 == s2
TEST_CASE.assertEqual(s1, s2)


def check_xml(s1, s2):
s1 = normalize_xml(s1)
s2 = normalize_xml(s2)
assert s1 == s2, "XML Output Changed"
TEST_CASE.assertEqual(s1, s2, "XML Output Changed")


def normalize_html(s):
Expand All @@ -115,15 +122,23 @@ def normalize_xml(s):
output_dir = os.path.join(HERE, 'output')


def _open(filename, mode):
if six.PY3:
# Define explicit encoding for windows platform
return open(filename, mode, encoding='utf-8')
else:
return open(filename, mode)


def read_input(filename):
filename = os.path.join(input_dir, filename)
with open(filename, 'r') as fd:
with _open(filename, 'r') as fd:
data = fd.read()
return data


def read_output(filename):
filename = os.path.join(output_dir, filename)
with open(filename, 'r') as fd:
with _open(filename, 'r') as fd:
data = fd.read()
return data
4 changes: 2 additions & 2 deletions src/Zope2/Startup/tests/test_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ def test_pid_filename(self):

conf, dummy = self.load_config_text(u"""\
instancehome <<INSTANCE_HOME>>
pid-filename <<INSTANCE_HOME>>/Z5.pid
""")
pid-filename <<INSTANCE_HOME>>{sep}Z5.pid
""".format(sep=os.path.sep))
expected = os.path.join(conf.instancehome, 'Z5.pid')
self.assertEqual(conf.pid_filename, expected)
11 changes: 8 additions & 3 deletions src/Zope2/utilities/tests/test_zconsole.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ def test_debug(self):

def test_runscript(self):
script = os.path.join(self.instancedir, 'test_script.py')
# Use a backslash to fake windows paths under linux
bar = r'\bar'
with open(script, 'w') as scriptfile:
scriptfile.write(test_script)
try:
Expand All @@ -79,15 +81,18 @@ def test_runscript(self):
'run',
self.zopeconf,
script,
'bar', 'baz']
bar, 'baz']
sys.stdout = StringIO()
runscript(self.zopeconf, script, 'bar', 'baz')
sys.stdout.seek(0)
got = sys.stdout.read()
finally:
sys.argv = self.stored_sys_argv
sys.stdout = self.stored_stdout
# We get double escape for backslash \ on windows from test script,
# so we have the raw string in there.
expected = (
"42\n['run', '{}', '{}', 'bar', 'baz']\nPropertyManager\n").format(
self.zopeconf, script)
"42\n"
r"['run', {!r}, {!r}, {!r}, 'baz']"
"\nPropertyManager\n".format(self.zopeconf, script, bar))
self.assertEqual(expected, got)
3 changes: 2 additions & 1 deletion tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ envlist =
coverage-report

[testenv]
coverage_run = {env:WIN_COV:coverage run}
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 alltests
coverage run {envbindir}/alltests {posargs:-vc}
{[testenv]coverage_run} {envdir}/bin/alltests {posargs:-vc}
skip_install = true
deps =
-cconstraints.txt
Expand Down

0 comments on commit c46fe7c

Please sign in to comment.