Skip to content

Commit

Permalink
rename to cromulent for pypi uniqueness
Browse files Browse the repository at this point in the history
  • Loading branch information
azaroth42 committed Dec 20, 2016
1 parent 2cf1680 commit b38e1b9
Show file tree
Hide file tree
Showing 10 changed files with 157 additions and 121 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@ notifications:
recipients:
- [email protected]
script:
coverage run --source=crom setup.py test
coverage run --source=cromulent setup.py test
after_success:
coveralls
55 changes: 49 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,53 @@
[![Build Status](https://travis-ci.org/azaroth42/crom.svg?branch=master)](https://travis-ci.org/azaroth42/crom) [![Coverage Status](https://coveralls.io/repos/github/azaroth42/crom/badge.svg?branch=master)](https://coveralls.io/github/azaroth42/crom?branch=master)

# Python-CIDOC-ORM
Python library to make creation of CIDOC CRM easier by mapping classes/predicates to python objects
# Cromulent

# Status
Very Alpha. Extracted the interesting bits from IIIF-Prezi and refactored to be a bit stricter.
Parses an extraction of the CRM ontology as TSV to generate class hierarchy and property/predicate mapping.
A Python library to make creation of CIDOC CRM easier by mapping classes/predicates to python objects/properties, thereby making the CRM "CRoMulent", a Simpsons neologism for "acceptable" or "fine".

## Status

Alpha. Active development and compatibility breaking changes to be expected as we use it in anger in various projects at The Getty, and beyond.

## How to Use It

### Basic Usage

```python
from cromulent.model import Person
p = Person("Mother")
p2 = Person("Son")
p3 = Person("Daughter")
p.parent_of = p2
p.parent_of = p3
```

Some tricks to know:

* Assigning to the same property repeatedly does NOT overwrite the value, instead it appends. To overwrite a value, instead set it to a false value first.

### Factory settings

There are several settings for how the module works, which are managed by a `factory` object in model.

* `base_url` The base url on to which to append any slug given when an object is created
* `base_dir` The base directory into which to write files, via factory.toFile()
* `default_lang` The code for the default language to use on text values
* `context_uri` The URI to use for `@context` in the JSON-LD serialization
* `debug_level` Settings for debugging errors and warnings, defaults to "warn"
* `log_stream` An object implementing the stream API to write log messages to, defaults to sys.stderr
* `materialize_inverses` Should the inverse relationships be set automatically, defaults to False
* `filename_extension` The extension to use on files written via toFile(), defaults to ".json"
* `full_names` Should the serialization use the full CRM names for classes and properties instead of the more readable ones defined in the mapping, defaults to False
* `validate_properties` Should the model be validated at run time when setting properties, defaults to True (this allows you to save processing time once you're certain your code does the right thing)

Note that factories are NOT thread safe during serialization. A property on the factory is used to maintain which objects have been serialized already, to avoid infinite recursion in a cyclic graph. Create a new factory object per thread if necessary.


## How it Works

At import time, the library parses the vocabulary data file (data/crm_vocab.tsv) and creates Python classes in the module's global scope from each of the defined RDF classes. The names of the classes are intended to be easy to use and remember, not necessarily identical to the CRM ontology's names. It also records the properties that can be used with that class, and at run time checks whether the property is defined and that the value fits the defined range.

## Hacking

You can change the mapping by tweaking `build_tsv/vocab_reader.py` and rerunning it to build a new TSV input file.

Collaboration and feedback welcome!
File renamed without changes.
File renamed without changes.
23 changes: 10 additions & 13 deletions crom/crom.py → cromulent/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,19 +66,17 @@ class CidocFactory(object):
def __init__(self, base_url="", base_dir="", lang="", context="", full_names=False):
self.base_url = base_url
self.base_dir = base_dir
self.default_lang = lang
self.context_uri = context

self.debug_level = "warn"
self.log_stream = sys.stderr
self.materialize_inverses = False
self.filename_extension = ".json"

self.namespaces = {}
self.class_map = {}
self.property_map = {}

self.materialize_inverses = False
self.full_names = False
self.validate_properties = True
self.default_lang = lang

self.filename_extension = ".json"
self.context_uri = context

self.key_order_hash = {"@context": 0, "id": 1, "type": 2, "classified_as": 3,
"label": 4, "value": 4, "note": 5, "description": 5, "identified_by": 10,
Expand All @@ -92,8 +90,6 @@ def __init__(self, base_url="", base_dir="", lang="", context="", full_names=Fal
"paid_amount": 50, "paid_from": 51, "paid_to": 52,
"transferred_title_of": 50, "transferred_title_from": 51, "transferred_title_to": 52,



"offering_price": 48, "sales_price": 49,

"consists_of": 100, "composed_of": 101
Expand Down Expand Up @@ -258,9 +254,10 @@ def __setattr__(self, which, value):
elif which[0] == "_" or not value:
object.__setattr__(self, which, value)
else:
ok = self._check_prop(which, value)
if not ok:
raise DataError("Can't set non-standard field '%s' on resource of type '%s'" % (which, self._type))
if self._factory.validate_properties:
ok = self._check_prop(which, value)
if not ok:
raise DataError("Can't set non-standard field '%s' on resource of type '%s'" % (which, self._type))

# Allow per class setter functions to do extra magic
if hasattr(self, which) and hasattr(self, 'set_%s' % which):
Expand Down
2 changes: 1 addition & 1 deletion crom/vocab_mapping.py → cromulent/vocab.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@

import inspect
from .crom import Identifier, Mark, ManMadeObject, Type, \
from .model import Identifier, Mark, ManMadeObject, Type, \
Person, Material, MeasurementUnit, Place, Dimension, \
ConceptualObject, TimeSpan, Actor, PhysicalThing, \
LinguisticObject, InformationObject, SpatialCoordinates, \
Expand Down
10 changes: 5 additions & 5 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,18 @@
install_requires = []

setup(
name = 'crom',
packages = ['crom'],
name = 'cromulent',
packages = ['cromulent'],
include_package_data = True,
package_data = {
'crom': ['data/crm_vocab.tsv']
'cromulent': ['data/crm_vocab.tsv']
},
test_suite="tests",
version = '0.0.3',
version = '0.1',
description = 'A library for mapping CIDOC-CRM classes to Python objects',
author = 'Getty Research Institute, Rob Sanderson',
author_email = '[email protected]',
url = 'https://github.com/gri-is/crom',
url = 'https://github.com/gri-is/cromulent',
install_requires=install_requires,
classifiers = [
"Programming Language :: Python",
Expand Down
105 changes: 51 additions & 54 deletions tests/test_crom.py → tests/test_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,73 +9,73 @@
# 2.6
from ordereddict import OrderedDict

from crom import crom
from cromulent import model

class TestFactorySetupDefaults(unittest.TestCase):

def test_init_defaults(self):
self.assertEqual(crom.factory.base_url, 'http://lod.example.org/museum/')
self.assertEqual(crom.factory.debug_level, 'warn')
self.assertEqual(crom.factory.log_stream, sys.stderr)
self.assertFalse(crom.factory.materialize_inverses)
self.assertFalse(crom.factory.full_names)
self.assertEqual(model.factory.base_url, 'http://lod.example.org/museum/')
self.assertEqual(model.factory.debug_level, 'warn')
self.assertEqual(model.factory.log_stream, sys.stderr)
self.assertFalse(model.factory.materialize_inverses)
self.assertFalse(model.factory.full_names)
# Don't test orders, as these will change repeatedly

class TestFactorySetup(unittest.TestCase):

def setUp(self):
crom.factory.base_url = 'http://data.getty.edu/provenance/'
crom.factory.base_dir = 'tests/provenance_base_dir'
crom.factory.default_lang = 'en'
crom.factory.context_uri = 'http://www.cidoc-crm.org/cidoc-crm/'
model.factory.base_url = 'http://data.getty.edu/provenance/'
model.factory.base_dir = 'tests/provenance_base_dir'
model.factory.default_lang = 'en'
model.factory.context_uri = 'http://www.cidoc-crm.org/cidoc-crm/'

def tearDown(self):
crom.factory.base_url = 'http://lod.example.org/museum/'
crom.factory.log_stream = sys.stderr
crom.factory.debug_level = 'warn'
model.factory.base_url = 'http://lod.example.org/museum/'
model.factory.log_stream = sys.stderr
model.factory.debug_level = 'warn'

def test_base_url(self):
self.assertEqual(crom.factory.base_url, 'http://data.getty.edu/provenance/')
self.assertEqual(model.factory.base_url, 'http://data.getty.edu/provenance/')

def test_base_dir(self):
self.assertEqual(crom.factory.base_dir, 'tests/provenance_base_dir')
self.assertEqual(model.factory.base_dir, 'tests/provenance_base_dir')

def test_default_lang(self):
self.assertEqual(crom.factory.default_lang, 'en')
self.assertEqual(model.factory.default_lang, 'en')

def test_context_uri(self):
self.assertEqual(crom.factory.context_uri, 'http://www.cidoc-crm.org/cidoc-crm/')
self.assertEqual(model.factory.context_uri, 'http://www.cidoc-crm.org/cidoc-crm/')

def test_set_debug_stream(self):
strm = open('err_output', 'w')
crom.factory.set_debug_stream(strm)
self.assertEqual(crom.factory.log_stream, strm)
model.factory.set_debug_stream(strm)
self.assertEqual(model.factory.log_stream, strm)

def test_set_debug(self):
crom.factory.set_debug('error_on_warning')
self.assertEqual(crom.factory.debug_level, 'error_on_warning')
self.assertRaises(crom.ConfigurationError, crom.factory.set_debug, 'xxx')
model.factory.set_debug('error_on_warning')
self.assertEqual(model.factory.debug_level, 'error_on_warning')
self.assertRaises(model.ConfigurationError, model.factory.set_debug, 'xxx')

class TestFactorySerialization(unittest.TestCase):

def setUp(self):
self.collection = crom.InformationObject('collection')
self.collection = model.InformationObject('collection')

def test_toJSON(self):
expect = OrderedDict([('id', u'http://lod.example.org/museum/InformationObject/collection'),
('type', 'InformationObject')])
outj = crom.factory.toJSON(self.collection)
outj = model.factory.toJSON(self.collection)
self.assertEqual(expect, outj)

def test_toString(self):
expect = u'{"id":"http://lod.example.org/museum/InformationObject/collection","type":"InformationObject"}'
outs = crom.factory.toString(self.collection)
outs = model.factory.toString(self.collection)
self.assertEqual(expect, outs)

def test_toFile(self):
self.assertRaises(crom.ConfigurationError, crom.factory.toFile, self.collection)
crom.factory.base_dir = 'tests'
crom.factory.toFile(self.collection)
self.assertRaises(model.ConfigurationError, model.factory.toFile, self.collection)
model.factory.base_dir = 'tests'
model.factory.toFile(self.collection)
self.assertTrue(os.path.isfile('tests/InformationObject/collection.json'))
shutil.rmtree('tests/InformationObject')

Expand All @@ -85,8 +85,8 @@ def test_process_tsv(self):
expect = {u'subs': [u'E84_Information_Carrier'], u'label': u'Man-Made Object', u'className': u'ManMadeObject',
u'subOf': u'E19_Physical_Object|E24_Physical_Man-Made_Thing', u'props': [], u'class': None,
u'desc': u'This class comprises physical objects purposely created by human activity.\\nNo assumptions are made as to the extent of modification required to justify regarding an object as man-made. For example, an inscribed piece of rock or a preserved butterfly are both regarded as instances of E22 Man-Made Object.'}
fn = 'crom/data/crm_vocab.tsv'
vocabData = crom.process_tsv(fn)
fn = 'cromulent/data/crm_vocab.tsv'
vocabData = model.process_tsv(fn)
man_made = vocabData['E22_Man-Made_Object']
self.assertEqual(expect, man_made)

Expand All @@ -97,8 +97,8 @@ def test_build_classes(self):
fh = open('tests/temp.tsv', 'w')
fh.write(tsv)
fh.close()
crom.build_classes("tests/temp.tsv", "ClassName_full")
from crom.crom import ClassName_py
model.build_classes("tests/temp.tsv", "ClassName_full")
from cromulent.model import ClassName_py
self.assertEqual('Class Description', ClassName_py._description)
os.remove('tests/temp.tsv')

Expand All @@ -109,17 +109,17 @@ def test_build_class(self):
fh = open('tests/temp.tsv', 'w')
fh.write(tsv)
fh.close()
vocabData = crom.process_tsv('tests/temp.tsv')
crom.build_class('ClassName_full', crom.BaseResource, vocabData)
from crom.crom import ClassName_py2
vocabData = model.process_tsv('tests/temp.tsv')
model.build_class('ClassName_full', model.BaseResource, vocabData)
from cromulent.model import ClassName_py2
self.assertEqual('Class Description', ClassName_py2._description)
os.remove('tests/temp.tsv')

class TestBaseResource(unittest.TestCase):

def setUp(self):
self.artist = crom.Person('00001', 'Jane Doe')
self.son = crom.Person('00002', 'John Doe')
self.artist = model.Person('00001', 'Jane Doe')
self.son = model.Person('00002', 'John Doe')

def test_init(self):
self.assertEqual(self.artist.id, 'http://lod.example.org/museum/Person/00001')
Expand All @@ -142,7 +142,7 @@ def test_list_all_props(self):
props = self.artist._list_all_props()
(lbl, cl) = sorted(props.items())[0]
self.assertEqual('acquired_title_through', lbl)
self.assertEqual(crom.Acquisition, cl)
self.assertEqual(model.Acquisition, cl)

def test_check_reference(self):
self.assertTrue(self.artist._check_reference('http'))
Expand All @@ -152,25 +152,25 @@ def test_check_reference(self):
self.assertTrue(self.artist._check_reference(self.son))
self.assertTrue(self.artist._check_reference(['http']))
self.assertFalse(self.artist._check_reference(['xxx', 'yyy']))
self.assertTrue(self.artist._check_reference(crom.Person))
self.assertTrue(self.artist._check_reference(model.Person))

class TestMagicMethods(unittest.TestCase):

def test_set_magic_lang(self):
crom.factory.default_lang = 'en'
crom.Person._lang_properties = ['label', 'description']
artist = crom.Person('00001', 'Jane Doe')
model.factory.default_lang = 'en'
model.Person._lang_properties = ['label', 'description']
artist = model.Person('00001', 'Jane Doe')
self.assertEqual(artist.label, {'en': 'Jane Doe'})
artist._set_magic_lang('label', 'Janey')
self.assertEqual(artist.label, {'en': ['Jane Doe', 'Janey']})
son = crom.Person('00002', 'John Doe')
self.assertRaises(crom.DataError, artist._set_magic_lang, 'parent_of', son)
son = model.Person('00002', 'John Doe')
self.assertRaises(model.DataError, artist._set_magic_lang, 'parent_of', son)

def test_set_magic_resource(self):
artist = crom.Person('00001', 'Jane Doe')
son = crom.Person('00002', 'John Doe')
daughter = crom.Person('00002', 'Jenny Doe')
son2 = crom.Person('00002', 'Jim Doe')
artist = model.Person('00001', 'Jane Doe')
son = model.Person('00002', 'John Doe')
daughter = model.Person('00002', 'Jenny Doe')
son2 = model.Person('00002', 'Jim Doe')
artist._set_magic_resource('parent_of', son)
self.assertEqual(artist.parent_of, son)
artist._set_magic_resource('parent_of', daughter)
Expand All @@ -193,15 +193,12 @@ def test_set_magic_resource(self):
self.assertTrue(son2 in artist.parent_of)

def test_set_magic_resource_inverse(self):
crom.factory.materialize_inverses = True
artist = crom.Person('00001', 'Jane Doe')
son = crom.Person('00002', 'John Doe')
model.factory.materialize_inverses = True
artist = model.Person('00001', 'Jane Doe')
son = model.Person('00002', 'John Doe')
artist._set_magic_resource('parent_of', son)
self.assertEqual(son.parent, artist)





if __name__ == '__main__':
unittest.main()
Loading

0 comments on commit b38e1b9

Please sign in to comment.