Skip to content

Commit

Permalink
Issue #5 make fixes for typo mistake in various places
Browse files Browse the repository at this point in the history
* tests structure has been created, which dedicated to issues
* make test coverage for Issue 5 [nazrulworld]
  • Loading branch information
nazrulworld committed Jul 18, 2019
1 parent 19623b4 commit 348eabb
Show file tree
Hide file tree
Showing 10 changed files with 93 additions and 66 deletions.
5 changes: 4 additions & 1 deletion Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ url = "https://pypi.org/simple"
verify_ssl = true

[dev-packages]
fhir-resources = {path = ".",extras = ["test"],editable = true}
fhir-resources = {editable = true,extras = ["test"],path = "."}
Jinja2 = {version = ">=2.9.5"}
MarkupSafe = {version = "==0.23"}
requests = {version = ">=2.13.0"}
Expand All @@ -17,3 +17,6 @@ isodate = "*"

[requires]
python_version = "3.7"

[pipenv]
allow_prereleases = true
2 changes: 1 addition & 1 deletion fhir-parser
116 changes: 58 additions & 58 deletions fhir/resources/DSTU2/fhirabstractbase.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ class FHIRValidationError(Exception):
""" Exception raised when one or more errors occurred during model
validation.
"""

def __init__(self, errors, path=None):
""" Initializer.
:param errors: List of Exception instances. Also accepts a string,
which is converted to a TypeError.
:param str path: The property path on the object where errors occurred
Expand All @@ -23,16 +23,16 @@ def __init__(self, errors, path=None):
errors = [TypeError(errors)]
msgs = "\n ".join([str(e).replace("\n", "\n ") for e in errors])
message = "{}:\n {}".format(path or "{root}", msgs)

super(FHIRValidationError, self).__init__(message)

self.errors = errors
""" A list of validation errors encountered. Typically contains
TypeError, KeyError, possibly AttributeError and others. """

self.path = path
""" The path on the object where the errors occurred. """

def prefixed(self, path_prefix):
""" Creates a new instance of the receiver, with the given path prefix
applied. """
Expand All @@ -43,22 +43,22 @@ def prefixed(self, path_prefix):
class FHIRAbstractBase(object):
""" Abstract base class for all FHIR elements.
"""

def __init__(self, jsondict=None, strict=True):
""" Initializer. If strict is true, raises on errors, otherwise uses
`logging.warning()`.
:raises: FHIRValidationError on validation errors, unless strict is False
:param dict jsondict: A JSON dictionary to use for initialization
:param bool strict: If True (the default), invalid variables will raise a TypeError
"""

self._resolved = None
""" Dictionary of resolved resources. """

self._owner = None
""" Points to the parent resource, if there is one. """

if jsondict is not None:
if strict:
self.update_with_json(jsondict)
Expand All @@ -68,51 +68,51 @@ def __init__(self, jsondict=None, strict=True):
except FHIRValidationError as e:
for err in e.errors:
logging.warning(err)


# MARK: Instantiation from JSON

@classmethod
def with_json(cls, jsonobj):
""" Initialize an element from a JSON dictionary or array.
If the JSON dictionary has a "resourceType" entry and the specified
resource type is not the receiving classes type, uses
`FHIRElementFactory` to return a correct class instance.
:raises: TypeError on anything but dict or list of dicts
:raises: FHIRValidationError if instantiation fails
:param jsonobj: A dict or list of dicts to instantiate from
:returns: An instance or a list of instances created from JSON data
"""
if isinstance(jsonobj, dict):
return cls._with_json_dict(jsonobj)

if isinstance(jsonobj, list):
return [cls._with_json_dict(jsondict) for jsondict in jsonobj]

raise TypeError("`with_json()` on {} only takes dict or list of dict, but you provided {}"
.format(cls, type(jsonobj)))

@classmethod
def _with_json_dict(cls, jsondict):
""" Internal method to instantiate from JSON dictionary.
:raises: TypeError on anything but dict
:raises: FHIRValidationError if instantiation fails
:returns: An instance created from dictionary data
"""
if not isinstance(jsondict, dict):
raise TypeError("Can only use `_with_json_dict()` on {} with a dictionary, got {}"
.format(type(self), type(jsondict)))
raise TypeError("Can only use `_with_json_dict()` on {0} with a dictionary, but got {1}"
.format(cls, type(jsondict)))
return cls(jsondict)

@classmethod
def with_json_and_owner(cls, jsonobj, owner):
""" Instantiates by forwarding to `with_json()`, then remembers the
"owner" of the instantiated elements. The "owner" is the resource
containing the receiver and is used to resolve contained resources.
:raises: TypeError on anything but dict or list of dicts
:raises: FHIRValidationError if instantiation fails
:param dict jsonobj: Decoded JSON dictionary (or list thereof)
Expand All @@ -125,32 +125,32 @@ def with_json_and_owner(cls, jsonobj, owner):
inst._owner = owner
else:
instance._owner = owner

return instance


# MARK: (De)Serialization

def elementProperties(self):
""" Returns a list of tuples, one tuple for each property that should
be serialized, as: ("name", "json_name", type, is_list, "of_many", not_optional)
"""
return []

def update_with_json(self, jsondict):
""" Update the receiver with data in a JSON dictionary.
:raises: FHIRValidationError on validation errors
:param dict jsondict: The JSON dictionary to use to update the receiver
:returns: None on success, a list of errors if there were errors
"""
if jsondict is None:
return

if not isinstance(jsondict, dict):
raise FHIRValidationError("Non-dict type {} fed to `update_with_json` on {}"
.format(type(jsondict), type(self)))

# loop all registered properties and instantiate
errs = []
found = set(['resourceType', 'fhir_comments'])
Expand All @@ -160,7 +160,7 @@ def update_with_json(self, jsondict):
if not_optional:
nonoptionals.add(of_many or jsname)
continue

# bring the value in shape
err = None
value = jsondict[jsname]
Expand All @@ -170,7 +170,7 @@ def update_with_json(self, jsondict):
except Exception as e:
value = None
err = e

# got a value, test if it is of required type and assign
if value is not None:
testval = value
Expand All @@ -181,63 +181,63 @@ def update_with_json(self, jsondict):
testval = None
else:
testval = value[0] if value and len(value) > 0 else None

if testval is not None and not self._matches_type(testval, typ):
err = TypeError("Wrong type {} for property \"{}\" on {}, expecting {}"
.format(type(testval), name, type(self), typ))
else:
setattr(self, name, value)
# TODO: look at `_name` if this is a primitive

if err is not None:
errs.append(err.prefixed(name) if isinstance(err, FHIRValidationError) else FHIRValidationError([err], name))

found.add(jsname)
found.add('_'+jsname)
if of_many is not None:
found.add(of_many)

# were there missing non-optional entries?
if len(nonoptionals - found) > 0:
for miss in nonoptionals - found:
errs.append(KeyError("Non-optional property \"{}\" on {} is missing"
.format(miss, self)))

# were there superfluous dictionary keys?
if len(set(jsondict.keys()) - found) > 0:
for supflu in set(jsondict.keys()) - found:
errs.append(AttributeError("Superfluous entry \"{}\" in data for {}"
.format(supflu, self)))

if len(errs) > 0:
raise FHIRValidationError(errs)

def as_json(self):
""" Serializes to JSON by inspecting `elementProperties()` and creating
a JSON dictionary of all registered properties. Checks:
- whether required properties are not None (and lists not empty)
- whether not-None properties are of the correct type
:raises: FHIRValidationError if properties have the wrong type or if
required properties are empty
:returns: A validated dict object that can be JSON serialized
"""
js = {}
errs = []

# JSONify all registered properties
found = set()
nonoptionals = set()
for name, jsname, typ, is_list, of_many, not_optional in self.elementProperties():
if not_optional:
nonoptionals.add(of_many or jsname)

err = None
value = getattr(self, name)
if value is None:
continue

if is_list:
if not isinstance(value, list):
err = TypeError("Expecting property \"{}\" on {} to be list, but is {}"
Expand Down Expand Up @@ -265,20 +265,20 @@ def as_json(self):
js[jsname] = value.as_json() if hasattr(value, 'as_json') else value
except FHIRValidationError as e:
err = e.prefixed(name)

if err is not None:
errs.append(err if isinstance(err, FHIRValidationError) else FHIRValidationError([err], name))

# any missing non-optionals?
if len(nonoptionals - found) > 0:
for nonop in nonoptionals - found:
errs.append(KeyError("Property \"{}\" on {} is not optional, you must provide a value for it"
.format(nonop, self)))

if len(errs) > 0:
raise FHIRValidationError(errs)
return js

def _matches_type(self, value, typ):
if value is None:
return True
Expand All @@ -289,10 +289,10 @@ def _matches_type(self, value, typ):
if (sys.version_info < (3, 0)) and (str == typ or unicode == typ):
return (isinstance(value, str) or isinstance(value, unicode))
return False


# MARK: Handling References

def owningResource(self):
""" Walks the owner hierarchy and returns the next parent that is a
`DomainResource` instance.
Expand All @@ -301,7 +301,7 @@ def owningResource(self):
while owner is not None and not hasattr(owner, "contained"):
owner = owner._owner
return owner

def owningBundle(self):
""" Walks the owner hierarchy and returns the next parent that is a
`Bundle` instance.
Expand All @@ -310,25 +310,25 @@ def owningBundle(self):
while owner is not None and not 'Bundle' == owner.resource_name:
owner = owner._owner
return owner

def resolvedReference(self, refid):
""" Returns the resolved reference with the given id, if it has been
resolved already. If it hasn't, forwards the call to its owner if it
has one.
You should probably use `resolve()` on the `FHIRReference` itself.
:param refid: The id of the resource to resolve
:returns: An instance of `Resource`, if it was found
"""
if self._resolved and refid in self._resolved:
return self._resolved[refid]
return self._owner.resolvedReference(refid) if self._owner is not None else None

def didResolveReference(self, refid, resolved):
""" Called by `FHIRResource` when it resolves a reference. Stores the
resolved reference into the `_resolved` dictionary.
:param refid: The id of the resource that was resolved
:param refid: The resolved resource, ready to be cached
"""
Expand Down
4 changes: 2 additions & 2 deletions fhir/resources/STU3/fhirabstractbase.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,8 @@ def _with_json_dict(cls, jsondict):
:returns: An instance created from dictionary data
"""
if not isinstance(jsondict, dict):
raise TypeError("Can only use `_with_json_dict()` on {} with a dictionary, got {}"
.format(type(self), type(jsondict)))
raise TypeError("Can only use `_with_json_dict()` on {0} with a dictionary, but got {1}"
.format(cls, type(jsondict)))
return cls(jsondict)

@classmethod
Expand Down
4 changes: 2 additions & 2 deletions fhir/resources/fhirabstractbase.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,8 @@ def _with_json_dict(cls, jsondict):
:returns: An instance created from dictionary data
"""
if not isinstance(jsondict, dict):
raise TypeError("Can only use `_with_json_dict()` on {} with a dictionary, got {}"
.format(type(self), type(jsondict)))
raise TypeError("Can only use `_with_json_dict()` on {0} with a dictionary, but got {1}"
.format(cls, type(jsondict)))
return cls(jsondict)

@classmethod
Expand Down
4 changes: 2 additions & 2 deletions script/templates/fhirabstractbase.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,8 @@ def _with_json_dict(cls, jsondict):
:returns: An instance created from dictionary data
"""
if not isinstance(jsondict, dict):
raise TypeError("Can only use `_with_json_dict()` on {} with a dictionary, got {}"
.format(type(self), type(jsondict)))
raise TypeError("Can only use `_with_json_dict()` on {0} with a dictionary, but got {1}"
.format(cls, type(jsondict)))
return cls(jsondict)

@classmethod
Expand Down
Empty file added tests/__init__.py
Empty file.
1 change: 1 addition & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pytest_plugins = ['fixtures']

0 comments on commit 348eabb

Please sign in to comment.