Huge code and copyright cleanup.

git-svn-id: http://svn.32rwr.info/radicale/trunk@9 74e4794c-479d-4a33-9dda-c6c359d70f12
This commit is contained in:
(no author) 2009-07-27 15:04:54 +00:00
parent e7a5ef8c5d
commit 81f7201399
11 changed files with 196 additions and 185 deletions

View File

@ -2,7 +2,9 @@
# -*- coding: utf-8; indent-tabs-mode: nil; -*-
#
# This file is part of Radicale Server - Calendar Server
# Copyright © 2008 The Radicale Team
# Copyright © 2008-2009 Guillaume Ayoub
# Copyright © 2008 Nicolas Kandel
# Copyright © 2008 Pascal Halter
#
# This library is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -23,6 +25,12 @@
# TODO: Magage command-line options
# TODO: Forget twisted?
"""
Radicale Server entry point.
Launch the Radicale Serve according to the configuration.
"""
import sys
from twisted.web import server
from twisted.internet import reactor
@ -31,18 +39,14 @@ from twisted.python import log
import radicale
class ServerContextFactory(object):
"""
SSL context factory
"""
def getContext(self):
"""
Get SSL context for the HTTP server
"""
"""SSL context factory."""
def get_context(self):
"""Get SSL context for the HTTP server."""
from OpenSSL import SSL
ctx = SSL.Context(SSL.SSLv23_METHOD)
ctx.use_certificate_file(radicale.config.get("server", "certificate"))
ctx.use_privatekey_file(radicale.config.get("server", "privatekey"))
return ctx
context = SSL.Context(SSL.SSLv23_METHOD)
context.use_certificate_file(radicale.config.get("server", "certificate"))
context.use_privatekey_file(radicale.config.get("server", "privatekey"))
return context
log.startLogging(sys.stdout)
#log.startLogging(open(radicale.config.get("server", "log"), "w"))

View File

@ -1,7 +1,9 @@
# -*- coding: utf-8; indent-tabs-mode: nil; -*-
#
# This file is part of Radicale Server - Calendar Server
# Copyright © 2008 The Radicale Team
# Copyright © 2008-2009 Guillaume Ayoub
# Copyright © 2008 Nicolas Kandel
# Copyright © 2008 Pascal Halter
#
# This library is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -17,16 +19,15 @@
# along with Radicale. If not, see <http://www.gnu.org/licenses/>.
# TODO: Manage errors (see xmlutils)
# TODO: Forget twisted?
from twisted.web.resource import Resource
from twisted.web import http
import posixpath
import config
import support
import acl
import xmlutils
import calendar
@ -34,112 +35,88 @@ _users = acl.users()
_calendars = support.calendars()
class CalendarResource(Resource):
"""
Twisted resource for requests at calendar depth (/user/calendar)
"""
"""Twisted resource for requests at calendar depth (/user/calendar)."""
# Tell twisted this is a leaf for requests
isLeaf = True
def __init__(self, user, cal):
"""
Initialize resource creating a calendar object corresponding
to the stocked calendar named user/cal
"""Initialize by creating a calendar object.
The calendar object corresponds to the stocked calendar named
``user``/``cal``.
"""
Resource.__init__(self)
self.calendar = calendar.Calendar(user, cal)
def render_DELETE(self, request):
"""
Manage DELETE requests
"""
"""Manage DELETE ``request``."""
obj = request.getHeader("if-match")
answer = xmlutils.delete(obj, self.calendar, str(request.URLPath()))
request.setResponseCode(http.NO_CONTENT)
return answer
def render_OPTIONS(self, request):
"""
Manage OPTIONS requests
"""
"""Manage OPTIONS ``request``."""
request.setHeader("Allow", "DELETE, OPTIONS, PROPFIND, PUT, REPORT")
request.setHeader("DAV", "1, calendar-access")
request.setResponseCode(http.OK)
return ""
def render_PROPFIND(self, request):
"""
Manage PROPFIND requests
"""
xmlRequest = request.content.read()
answer = xmlutils.propfind(xmlRequest, self.calendar, str(request.URLPath()))
"""Manage PROPFIND ``request``."""
xml_request = request.content.read()
answer = xmlutils.propfind(xml_request, self.calendar, str(request.URLPath()))
request.setResponseCode(http.MULTI_STATUS)
return answer
def render_PUT(self, request):
"""
Manage PUT requests
"""
"""Manage PUT ``request``."""
# TODO: Improve charset detection
contentType = request.getHeader("content-type")
if contentType and "charset=" in contentType:
charset = contentType.split("charset=")[1].strip()
else:
charset = config.get("encoding", "request")
icalRequest = unicode(request.content.read(), charset)
ical_request = request.content.read().decode(charset)
obj = request.getHeader("if-match")
xmlutils.put(icalRequest, self.calendar, str(request.URLPath()), obj)
xmlutils.put(ical_request, self.calendar, str(request.URLPath()), obj)
request.setResponseCode(http.CREATED)
return ""
def render_REPORT(self, request):
"""
Manage REPORT requests
"""
xmlRequest = request.content.read()
answer = xmlutils.report(xmlRequest, self.calendar, str(request.URLPath()))
"""Manage REPORT ``request``."""
xml_request = request.content.read()
answer = xmlutils.report(xml_request, self.calendar, str(request.URLPath()))
request.setResponseCode(http.MULTI_STATUS)
return answer
class UserResource(Resource):
"""
Twisted resource for requests at user depth (/user)
"""
"""Twisted resource for requests at user depth (/user)."""
def __init__(self, user):
"""
Initialize resource by connecting children requests to
the user calendars resources
"""
"""Initialize by connecting requests to ``user`` calendars resources."""
Resource.__init__(self)
for cal in _calendars:
if cal.startswith("%s%s"%(user, posixpath.sep)):
calName = cal.split(posixpath.sep)[1]
self.putChild(calName, CalendarResource(user, cal))
cal_name = cal.split(posixpath.sep)[1]
self.putChild(cal_name, CalendarResource(user, cal))
def getChild(self, cal, request):
"""
Get calendar resource if user exists
"""
"""Get calendar resource if ``cal`` exists."""
if cal in _calendars:
return Resource.getChild(self, cal, request)
else:
return self
class HttpResource(Resource):
"""
Twisted resource for requests at root depth (/)
"""
"""Twisted resource for requests at root depth (/)."""
def __init__(self):
"""
Initialize resource by connecting children requests to
the users resources
"""
"""Initialize by connecting requests to the users resources."""
Resource.__init__(self)
for user in _users:
self.putChild(user, UserResource(user))
def getChild(self, user, request):
"""
Get user resource if user exists
"""
"""Get user resource if ``user`` exists."""
if user in _users:
return Resource.getChild(self, user, request)
else:

View File

@ -1,7 +1,9 @@
# -*- coding: utf-8; indent-tabs-mode: nil; -*-
#
# This file is part of Radicale Server - Calendar Server
# Copyright © 2008 The Radicale Team
# Copyright © 2008-2009 Guillaume Ayoub
# Copyright © 2008 Nicolas Kandel
# Copyright © 2008 Pascal Halter
#
# This library is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -16,6 +18,13 @@
# You should have received a copy of the GNU General Public License
# along with Radicale. If not, see <http://www.gnu.org/licenses/>.
"""
Users and rights management.
This module loads a list of users with access rights, according to the acl
configuration.
"""
from .. import config
_acl = __import__(config.get("acl", "type"), locals(), globals())

View File

@ -1,7 +1,9 @@
# -*- coding: utf-8; indent-tabs-mode: nil; -*-
#
# This file is part of Radicale Server - Calendar Server
# Copyright © 2008 The Radicale Team
# Copyright © 2008-2009 Guillaume Ayoub
# Copyright © 2008 Nicolas Kandel
# Copyright © 2008 Pascal Halter
#
# This library is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -16,10 +18,14 @@
# You should have received a copy of the GNU General Public License
# along with Radicale. If not, see <http://www.gnu.org/licenses/>.
"""
Fake ACL.
Just load the default user set in configuration, with no rights management.
"""
from .. import config
def users():
"""
Get the List of all Users
"""
return [config.get("acl", "defaultUser")]
"""Get the list of all users."""
return [config.get("acl", "user")]

View File

@ -1,7 +1,9 @@
# -*- coding: utf-8; indent-tabs-mode: nil; -*-
#
# This file is part of Radicale Server - Calendar Server
# Copyright © 2008 The Radicale Team
# Copyright © 2008-2009 Guillaume Ayoub
# Copyright © 2008 Nicolas Kandel
# Copyright © 2008 Pascal Halter
#
# This library is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -16,10 +18,17 @@
# You should have received a copy of the GNU General Public License
# along with Radicale. If not, see <http://www.gnu.org/licenses/>.
"""
Htpasswd ACL.
Load the list of users according to the htpasswd configuration.
"""
# TODO: Manage rights
from .. import config
def users():
"""
Get the List of all Users
"""
return [line.split(":")[0] for line in open(config.get("acl", "filename")).readlines()]
"""Get the list of all users."""
return [line.split(":")[0] for line
in open(config.get("acl", "filename")).readlines()]

View File

@ -1,7 +1,9 @@
# -*- coding: utf-8; indent-tabs-mode: nil; -*-
#
# This file is part of Radicale Server - Calendar Server
# Copyright © 2008 The Radicale Team
# Copyright © 2008-2009 Guillaume Ayoub
# Copyright © 2008 Nicolas Kandel
# Copyright © 2008 Pascal Halter
#
# This library is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -16,73 +18,68 @@
# You should have received a copy of the GNU General Public License
# along with Radicale. If not, see <http://www.gnu.org/licenses/>.
# TODO: Manage inheritance for classes
"""
Radicale calendar classes.
from time import time
Define the main classes of a calendar as seen from the server.
"""
import support
hash_tag = lambda vcalendar: str(hash(vcalendar))
class Calendar(object):
"""
Internal Calendar Class
"""
"""Internal calendar class."""
def __init__(self, user, cal):
# TODO: Use properties from the calendar
"""Initialize the calendar with ``cal`` and ``user`` parameters."""
# TODO: Use properties from the calendar configuration
self.encoding = "utf-8"
self.owner = "lize"
self.user = user
self.cal = cal
self.version = "2.0"
self.ctag = str(hash(self.vcalendar()))
self.ctag = hash_tag(self.vcalendar())
def append(self, vcalendar):
"""
Append vcalendar
"""
self.ctag = str(hash(self.vcalendar()))
"""Append vcalendar to the calendar."""
self.ctag = hash_tag(self.vcalendar())
support.append(self.cal, vcalendar)
def remove(self, uid):
"""
Remove Object Named uid
"""
self.ctag = str(hash(self.vcalendar()))
"""Remove object named ``uid`` from the calendar."""
self.ctag = hash_tag(self.vcalendar())
support.remove(self.cal, uid)
def replace(self, uid, vcalendar):
"""
Replace Objet Named uid by vcalendar
"""
self.ctag = str(hash(self.vcalendar()))
"""Replace objet named ``uid`` by ``vcalendar`` in the calendar."""
self.ctag = hash_tag(self.vcalendar())
support.remove(self.cal, uid)
support.append(self.cal, vcalendar)
def vcalendar(self):
"""Return unicode calendar from the calendar."""
return unicode(support.read(self.cal), self.encoding)
class Event(object):
"""
Internal Event Class
"""
# TODO: Fix the behaviour if no UID is given
"""Internal event class."""
def __init__(self, vcalendar):
"""Initialize event from ``vcalendar``."""
self.text = vcalendar
def etag(self):
return str(hash(self.text))
"""Return etag from event."""
return hash_tag(self.text)
class Header(object):
"""
Internal Headers Class
"""
"""Internal header class."""
def __init__(self, vcalendar):
"""Initialize header from ``vcalendar``."""
self.text = vcalendar
class Timezone(object):
"""
Internal Timezone Class
"""
"""Internal timezone class."""
def __init__(self, vcalendar):
"""Initialize timezone from ``vcalendar``."""
lines = vcalendar.splitlines()
for line in lines:
if line.startswith("TZID:"):
@ -92,12 +89,11 @@ class Timezone(object):
self.text = vcalendar
class Todo(object):
"""
Internal Todo Class
"""
# TODO: Fix the behaviour if no UID is given
"""Internal todo class."""
def __init__(self, vcalendar):
"""Initialize todo from ``vcalendar``."""
self.text = vcalendar
def etag(self):
return str(hash(self.text))
"""Return etag from todo."""
return hash_tag(self.text)

View File

@ -1,7 +1,9 @@
# -*- coding: utf-8; indent-tabs-mode: nil; -*-
#
# This file is part of Radicale Server - Calendar Server
# Copyright © 2008 The Radicale Team
# Copyright © 2008-2009 Guillaume Ayoub
# Copyright © 2008 Nicolas Kandel
# Copyright © 2008 Pascal Halter
#
# This library is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -16,9 +18,14 @@
# You should have received a copy of the GNU General Public License
# along with Radicale. If not, see <http://www.gnu.org/licenses/>.
"""
Radicale configuration module.
Give a configparser-like interface to read and write configuration.
"""
from ConfigParser import RawConfigParser as ConfigParser
# Default functions
_config = ConfigParser()
get = _config.get
set = _config.set
@ -28,7 +35,6 @@ getfloat = _config.getfloat
options = _config.options
items = _config.items
# Default config
_initial = {
"server": {
"type": "http",
@ -53,20 +59,19 @@ _initial = {
"acl": {
"type": "fake",
"filename": "/etc/radicale/users",
"defaultUser": "radicale",
"user": "radicale",
},
"support": {
"type": "plain",
"folder": "~/.config/radicale",
"defaultCalendar": "radicale/calendar",
"calendar": "radicale/calendar",
},
}
# Set the default config
for section, values in _initial.iteritems():
_config.add_section(section)
for key, value in values.iteritems():
_config.set(section, key, value)
# Set the user config
# TODO: Use abstract filename for other platforms
_config.read("/etc/radicale/config")

View File

@ -1,7 +1,9 @@
# -*- coding: utf-8; indent-tabs-mode: nil; -*-
#
# This file is part of Radicale Server - Calendar Server
# Copyright © 2008 The Radicale Team
# Copyright © 2008-2009 Guillaume Ayoub
# Copyright © 2008 Nicolas Kandel
# Copyright © 2008 Pascal Halter
#
# This library is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -16,16 +18,19 @@
# You should have received a copy of the GNU General Public License
# along with Radicale. If not, see <http://www.gnu.org/licenses/>.
"""
iCal parsing functions.
"""
# TODO: Manage filters (see xmlutils)
import calendar
def writeCalendar(headers=[calendar.Header("PRODID:-//The Radicale Team//NONSGML Radicale Server//EN"),
calendar.Header("VERSION:2.0")],
def write_calendar(headers=[
calendar.Header("PRODID:-//Radicale//NONSGML Radicale Server//EN"),
calendar.Header("VERSION:2.0")],
timezones=[], todos=[], events=[]):
"""
Create calendar from headers, timezones, todos, events
"""
"""Create calendar from ``headers``, ``timezones``, ``todos``, ``events``."""
# TODO: Manage encoding and EOL
cal = u"\n".join((
u"BEGIN:VCALENDAR",
@ -37,9 +42,7 @@ def writeCalendar(headers=[calendar.Header("PRODID:-//The Radicale Team//NONSGML
return u"\n".join([line for line in cal.splitlines() if line])
def headers(vcalendar):
"""
Find Headers Items in vcalendar
"""
"""Find Headers items in ``vcalendar``."""
headers = []
lines = vcalendar.splitlines()
@ -53,6 +56,10 @@ def headers(vcalendar):
return headers
def _parse(vcalendar, tag, obj):
"""Find ``tag`` items in ``vcalendar``.
Return a list of items of type ``obj``.
"""
items = []
lines = vcalendar.splitlines()

View File

@ -1,7 +1,9 @@
# -*- coding: utf-8; indent-tabs-mode: nil; -*-
#
# This file is part of Radicale Server - Calendar Server
# Copyright © 2008 The Radicale Team
# Copyright © 2008-2009 Guillaume Ayoub
# Copyright © 2008 Nicolas Kandel
# Copyright © 2008 Pascal Halter
#
# This library is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -16,6 +18,10 @@
# You should have received a copy of the GNU General Public License
# along with Radicale. If not, see <http://www.gnu.org/licenses/>.
"""
Calendar storage support configuration.
"""
from .. import config
_support = __import__(config.get("support", "type"), locals(), globals())

View File

@ -1,7 +1,9 @@
# -*- coding: utf-8; indent-tabs-mode: nil; -*-
#
# This file is part of Radicale Server - Calendar Server
# Copyright © 2008 The Radicale Team
# Copyright © 2008-2009 Guillaume Ayoub
# Copyright © 2008 Nicolas Kandel
# Copyright © 2008 Pascal Halter
#
# This library is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -16,6 +18,10 @@
# You should have received a copy of the GNU General Public License
# along with Radicale. If not, see <http://www.gnu.org/licenses/>.
"""
Plain text storage.
"""
import os
import posixpath
@ -25,9 +31,7 @@ from .. import config
_folder = os.path.expanduser(config.get("support", "folder"))
def calendars():
"""
List Available Calendars Paths
"""
"""List available calendars paths."""
calendars = []
for folder in os.listdir(_folder):
@ -37,40 +41,34 @@ def calendars():
return calendars
def mkcalendar(name):
"""
Write new calendar
"""
"""Write a new calendar called ``name``."""
user, cal = name.split(posixpath.sep)
if not os.path.exists(os.path.join(_folder, user)):
os.makedirs(os.path.join(_folder, user))
fd = open(os.path.join(_folder, user, cal), "w")
fd.write(ical.writeCalendar().encode(config.get("encoding", "stock")))
fd.write(ical.write_calendar().encode(config.get("encoding", "stock")))
def read(cal):
"""
Read cal
"""
"""Read calendar ``cal``."""
path = os.path.join(_folder, cal.replace(posixpath.sep, os.path.sep))
return open(path).read()
def append(cal, vcalendar):
"""
Append vcalendar to cal
"""
oldCalendar = unicode(read(cal), config.get("encoding", "stock"))
oldTzs = [tz.tzid for tz in ical.timezones(oldCalendar)]
"""Append ``vcalendar`` to ``cal``."""
old_calendar = unicode(read(cal), config.get("encoding", "stock"))
old_tzs = [tz.tzid for tz in ical.timezones(old_calendar)]
path = os.path.join(_folder, cal.replace(posixpath.sep, os.path.sep))
oldObjects = []
oldObjects.extend([event.etag() for event in ical.events(oldCalendar)])
oldObjects.extend([todo.etag() for todo in ical.todos(oldCalendar)])
old_objects = []
old_objects.extend([event.etag() for event in ical.events(old_calendar)])
old_objects.extend([todo.etag() for todo in ical.todos(old_calendar)])
objects = []
objects.extend(ical.events(vcalendar))
objects.extend(ical.todos(vcalendar))
for tz in ical.timezones(vcalendar):
if tz.tzid not in oldTzs:
if tz.tzid not in old_tzs:
# TODO: Manage position, encoding and EOL
fd = open(path)
lines = [line for line in fd.readlines() if line]
@ -84,7 +82,7 @@ def append(cal, vcalendar):
fd.close()
for obj in objects:
if obj.etag() not in oldObjects:
if obj.etag() not in old_objects:
# TODO: Manage position, encoding and EOL
fd = open(path)
lines = [line for line in fd.readlines() if line]
@ -98,9 +96,7 @@ def append(cal, vcalendar):
fd.close()
def remove(cal, etag):
"""
Remove object named uid from cal
"""
"""Remove object named ``etag`` from ``cal``."""
path = os.path.join(_folder, cal.replace(posixpath.sep, os.path.sep))
cal = unicode(read(cal), config.get("encoding", "stock"))
@ -111,11 +107,10 @@ def remove(cal, etag):
events = [event for event in ical.events(cal) if event.etag() != etag]
fd = open(path, "w")
fd.write(ical.writeCalendar(headers, timezones, todos, events).encode(config.get("encoding", "stock")))
fd.write(ical.write_calendar(headers, timezones, todos, events).encode(config.get("encoding", "stock")))
fd.close()
if config.get("support", "defaultCalendar"):
user, cal = config.get("support", "defaultCalendar").split(posixpath.sep)
if config.get("support", "calendar"):
user, cal = config.get("support", "calendar").split(posixpath.sep)
if not os.path.exists(os.path.join(_folder, user, cal)):
mkcalendar(config.get("support", "defaultCalendar"))
mkcalendar(config.get("support", "calendar"))

View File

@ -1,7 +1,9 @@
# -*- coding: utf-8; indent-tabs-mode: nil; -*-
#
# This file is part of Radicale Server - Calendar Server
# Copyright © 2008 The Radicale Team
# Copyright © 2008-2009 Guillaume Ayoub
# Copyright © 2008 Nicolas Kandel
# Copyright © 2008 Pascal Halter
#
# This library is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -17,7 +19,7 @@
# along with Radicale. If not, see <http://www.gnu.org/licenses/>.
"""
XML and iCal requests manager
XML and iCal requests manager.
Note that all these functions need to receive unicode objects for full
iCal requests (PUT) and string objects with charset correctly defined
@ -32,22 +34,21 @@ import xml.etree.ElementTree as ET
import config
import ical
# TODO: This is a well-known and accepted hack for ET
# TODO: This is a well-known and accepted hack for ET to avoid ET from renaming
# namespaces, which is accepted in XML norm but often not in XML
# readers. Is there another clean solution to force namespaces?
for key,value in config.items("namespace"):
ET._namespace_map[value] = key
def _tag(shortName, local):
"""
Get XML Clark notation {uri(shortName)}local
"""
return "{%s}%s"%(config.get("namespace", shortName), local)
def _tag(short_name, local):
"""Get XML Clark notation {uri(``short_name``)}``local``."""
return "{%s}%s"%(config.get("namespace", short_name), local)
def delete(obj, calendar, url):
"""
Read and answer DELETE requests
"""
# Read rfc4918-9.6 for info
"""Read and answer DELETE requests.
Read rfc4918-9.6 for info.
"""
# Reading request
calendar.remove(obj)
@ -66,14 +67,13 @@ def delete(obj, calendar, url):
return ET.tostring(multistatus, config.get("encoding", "request"))
def propfind(xmlRequest, calendar, url):
"""
Read and answer PROPFIND requests
"""
# Read rfc4918-9.1 for info
def propfind(xml_request, calendar, url):
"""Read and answer PROPFIND requests.
Read rfc4918-9.1 for info.
"""
# Reading request
root = ET.fromstring(xmlRequest)
root = ET.fromstring(xml_request)
propElement = root.find(_tag("D", "prop"))
propList = propElement.getchildren()
@ -117,9 +117,7 @@ def propfind(xmlRequest, calendar, url):
return ET.tostring(multistatus, config.get("encoding", "request"))
def put(icalRequest, calendar, url, obj):
"""
Read PUT requests
"""
"""Read PUT requests."""
if obj:
# PUT is modifying obj
calendar.replace(obj, icalRequest)
@ -127,14 +125,13 @@ def put(icalRequest, calendar, url, obj):
# PUT is adding a new object
calendar.append(icalRequest)
def report(xmlRequest, calendar, url):
"""
Read and answer REPORT requests
"""
# Read rfc3253-3.6 for info
def report(xml_request, calendar, url):
"""Read and answer REPORT requests.
Read rfc3253-3.6 for info.
"""
# Reading request
root = ET.fromstring(xmlRequest)
root = ET.fromstring(xml_request)
propElement = root.find(_tag("D", "prop"))
propList = propElement.getchildren()
@ -209,7 +206,7 @@ def report(xmlRequest, calendar, url):
if _tag("C", "calendar-data") in properties:
cdata = ET.Element(_tag("C", "calendar-data"))
# TODO: Maybe assume that events and todos are not the same
cdata.text = ical.writeCalendar(headers, timezones, [obj])
cdata.text = ical.write_calendar(headers, timezones, [obj])
prop.append(cdata)
status = ET.Element(_tag("D", "status"))