Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use 0001-01-01 as a placeholder for None publication, amendment and repeal dates #69

Merged
merged 1 commit into from
Apr 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ Change Log
- Cascade changes to FRBRlanguage into attachments
- Don't hardcode source
- Don't set ``contains="originalVersion"`` since it is the default value for that attribute.
- Use ``0001-01-01`` as a placeholder date for publication, amendment and repeal events with null dates

4.1.1
.....
Expand Down
24 changes: 19 additions & 5 deletions cobalt/akn.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,29 @@
}
DEFAULT_VERSION = '3.0'

# a placeholder date that indicates a null date, used in the XML where a date is required by may not be known
NULL_DATE = '0001-01-01'


def datestring(value):
""" Format a date as an XML-suitable string. If the date is None, uses NULL_DATE.
"""
if value is None:
return ""
return NULL_DATE
elif isinstance(value, str):
return value
else:
return "%04d-%02d-%02d" % (value.year, value.month, value.day)


def parsedate(value):
""" Parse an XML date string into a real date. If the value is the NULL_DATE, returns None.
"""
if value == NULL_DATE:
return None
return parse_date(value).date()


# Create a new objectify parser that doesn't remove blank text nodes
objectify_parser = etree.XMLParser()
objectify_parser.set_element_class_lookup(objectify.ObjectifyElementClassLookup())
Expand Down Expand Up @@ -91,7 +104,7 @@ def get_namespace(self):

raise ValueError(f"Expected to find one of the following Akoma Ntoso XML namespaces: {', '.join(akn_namespaces)}. Only these namespaces were found: {', '.join(namespaces)}")

def ensure_element(self, name, after, at=None):
def ensure_element(self, name, after, at=None, attribs=None):
""" Helper to get an element if it exists, or create it if it doesn't.

:param name: dotted path from `self` or `at`
Expand All @@ -101,7 +114,7 @@ def ensure_element(self, name, after, at=None):
node = self.get_element(name, root=at)
if node is None:
# TODO: what if nodes in the path don't exist?
node = self.make_element(name.split('.')[-1])
node = self.make_element(name.split('.')[-1], attribs)
after.addnext(node)

return node
Expand All @@ -123,8 +136,9 @@ def get_element(self, name, root=None):
return None
return node

def make_element(self, elem):
return getattr(self.maker, elem)()
def make_element(self, elem, attribs=None):
attribs = attribs or {}
return getattr(self.maker, elem)(**attribs)


class StructuredDocument(AkomaNtosoDocument):
Expand Down
22 changes: 11 additions & 11 deletions cobalt/hierarchical.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
from iso8601 import parse_date

from .akn import StructuredDocument, datestring
from .akn import StructuredDocument, datestring, NULL_DATE, parsedate


class HierarchicalStructure(StructuredDocument):
Expand Down Expand Up @@ -33,7 +31,7 @@ def publication_name(self):
@publication_name.setter
def publication_name(self, value):
value = value or ""
pub = self.ensure_element('meta.publication', after=self.meta.identification)
pub = self.ensure_publication()
pub.set('name', value)
pub.set('showAs', value)

Expand All @@ -43,13 +41,12 @@ def publication_date(self):
"""
pub = self.get_element('meta.publication')
if pub is not None and pub.get('date'):
return parse_date(pub.get('date')).date()
return parsedate(pub.get('date'))
return None

@publication_date.setter
def publication_date(self, value):
self.ensure_element('meta.publication', after=self.meta.identification)\
.set('date', datestring(value))
self.ensure_publication().set('date', datestring(value))

@property
def publication_number(self):
Expand All @@ -60,15 +57,14 @@ def publication_number(self):

@publication_number.setter
def publication_number(self, value):
self.ensure_element('meta.publication', after=self.meta.identification)\
.set('number', value or "")
self.ensure_publication().set('number', value or "")

@property
def amendments(self):
amendments = []

for e in self.meta.iterfind(f'.//{{{self.namespace}}}lifecycle/{{{self.namespace}}}eventRef[@type="amendment"]'):
date = parse_date(e.get('date')).date()
date = parsedate(e.get('date'))
event = AmendmentEvent(date=date)
amendments.append(event)

Expand Down Expand Up @@ -132,7 +128,7 @@ def amendments(self, value):
def repeal(self):
e = self.meta.find(f'.//{{{self.namespace}}}lifecycle/{{{self.namespace}}}eventRef[@type="repeal"]')
if e is not None:
date = parse_date(e.get('date')).date()
date = parsedate(e.get('date'))
event = RepealEvent(date=date)

id = e.get('source')[1:]
Expand Down Expand Up @@ -183,6 +179,10 @@ def repeal(self, value):
except AttributeError:
pass

def ensure_publication(self):
return self.ensure_element('meta.publication', after=self.meta.identification,
attribs={'showAs': '', 'name': '', 'date': NULL_DATE})


class AmendmentEvent(object):
""" An event that amended a document.
Expand Down
12 changes: 12 additions & 0 deletions tests/test_act.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,33 +22,43 @@ def test_empty_act(self):
def test_empty_body(self):
a = Act()
assert_not_equal(a.body.text, '')
assert_validates(a)

def test_publication_date(self):
a = Act()
assert_is_none(a.publication_date)
assert_validates(a)

a.publication_date = '2012-01-02'
assert_equal(datestring(a.publication_date), '2012-01-02')
assert_is_instance(a.publication_date, date)
assert_validates(a)

a.publication_date = None
assert_is_none(a.publication_date)
assert_validates(a)

def test_publication_number(self):
a = Act()
assert_is_none(a.publication_number)

a.publication_number = '1234'
assert_equal(a.publication_number, '1234')
assert_validates(a)

def test_publication_name(self):
a = Act()
assert_is_none(a.publication_name)

a.publication_name = 'Publication'
assert_equal(a.publication_name, 'Publication')
assert_validates(a)

def test_set_amendments(self):
a = Act()
a.frbr_uri = "/akn/za/act/1900/1"
a.amendments = [AmendmentEvent(date='2012-02-01', amending_uri='/za/act/1980/10', amending_title="Foo")]
assert_validates(a)

xml = a.to_xml(encoding='unicode', pretty_print=True)
xml = xml.replace(datestring(date.today()), 'TODAY')
Expand Down Expand Up @@ -104,6 +114,7 @@ def test_set_amendments(self):
AmendmentEvent(date='2012-02-01', amending_uri='/za/act/1980/22', amending_title="Corrected"),
AmendmentEvent(date='2013-03-03', amending_uri='/za/act/1990/5', amending_title="Bar"),
]
assert_validates(a)

xml = a.to_xml(encoding='unicode', pretty_print=True)
xml = xml.replace(datestring(date.today()), 'TODAY')
Expand Down Expand Up @@ -171,6 +182,7 @@ def test_set_amendments(self):

# clear them
a.amendments = []
assert_validates(a)
xml = a.to_xml(encoding='unicode', pretty_print=True)
xml = xml.replace(datestring(date.today()), 'TODAY')

Expand Down