Skip to content

Commit

Permalink
0.1.0 release.
Browse files Browse the repository at this point in the history
  • Loading branch information
chaseastewart committed Feb 25, 2024
1 parent e6f1d9b commit 7c6bed6
Show file tree
Hide file tree
Showing 10 changed files with 210 additions and 231 deletions.
21 changes: 21 additions & 0 deletions .github/workflows/greetings.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: Greetings

on: [pull_request, issues]

permissions:
pull-requests: write
issues: write

jobs:
greeting:
runs-on: ubuntu-latest
steps:
- uses: actions/first-interaction@v1
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
issue-message: >
Thanks for taking the time to open an issue.
We will have a look and answer as soon as we can.
pr-message: >
Thank you for opening a PR.
We will review the PR and provide feedback as soon as we can.
10 changes: 9 additions & 1 deletion CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
# Python FHIR Converter Change Log

## Version 0.22
## Version 0.1.0
- Fixed missing required AllergyIntolerance.reaction.manifestation for CDA->FHIR conversion. [_AllergyIntolerance.liquid](https://github.com/chaseastewart/fhir-converter/blob/69ca8f81cade9a480e624e09bfa3c4aa1663a2bf/fhir_converter/templates/ccda/Resource/_AllergyIntolerance.liquid#L23) incorrectly created an additional reaction for the severity. See [#3](https://github.com/chaseastewart/fhir-converter/issues/3)
- Remove time when timezone is not present in CDA datetime to conform with FHIR datetime. See [#2](https://github.com/chaseastewart/fhir-converter/issues/2)
- Added `CachingTemplateSystemLoader`. `TemplateSystemLoader` now extends `ChoiceLoader` to better align with `python-liquid`.
- Added `make_template_system_loader()`, a factory function that returns a `TemplateSystemLoader` or `CachingTemplateSystemLoader` depending on its arguments.
- Removed deprecated `ResourceLoader` and `CachedChoiceLoader`.
- Now depends on [python-liquid](https://pypi.org/project/python-liquid/) = 1.12.1.

## Version 0.0.22
- `ResourceLoader` and `CachedChoiceLoader` are depreciated and will be removed in 0.1.0. Use `PackageLoader` and `CachingChoiceLoader` instead.
- Run tests on mac, windows and ubuntu.
- Now depends on [python-liquid](https://pypi.org/project/python-liquid/) = 1.11.0.
8 changes: 4 additions & 4 deletions fhir_converter/hl7.py
Original file line number Diff line number Diff line change
Expand Up @@ -208,13 +208,13 @@ def to_fhir_dtm(dt: datetime, precision: Optional[FhirDtmPrecision] = None) -> s
"""
if precision is None:
precision = FhirDtmPrecision.SEC
# TODO HOUR, MIN, SEC truncation

iso_dtm = dt.isoformat(timespec=precision.timespec)
if dt.utcoffset() == zero_time_delta:
iso_dtm, offset = dt.isoformat(timespec=precision.timespec), dt.utcoffset()
if offset == zero_time_delta:
iso_dtm = iso_dtm[:-6] + "Z"

# TODO HOUR, MIN, SEC
if precision > FhirDtmPrecision.DAY:
if offset is not None and precision > FhirDtmPrecision.DAY:
return iso_dtm
elif precision > FhirDtmPrecision.MONTH:
return iso_dtm[: FhirDtmPrecision.DAY]
Expand Down
163 changes: 68 additions & 95 deletions fhir_converter/loaders.py
Original file line number Diff line number Diff line change
@@ -1,113 +1,23 @@
from __future__ import annotations

from pathlib import Path
from typing import Callable, List
from typing import List, Optional, Sequence

from importlib_resources import Package, files
from liquid import BoundTemplate, ChoiceLoader, Context, Environment
from liquid import ChoiceLoader, Context, Environment
from liquid.builtin.loaders.mixins import CachingLoaderMixin
from liquid.exceptions import TemplateNotFound
from liquid.loaders import BaseLoader, TemplateNamespace, TemplateSource
from typing_extensions import deprecated
from liquid.loaders import BaseLoader, TemplateSource


@deprecated(
"ResourceLoader is deprecated and scheduled for removal "
"in a future version. Use PackageLoader instead"
)
class ResourceLoader(BaseLoader):
"""A template loader that will load templates from the package resources
Args:
search_package (Package): The package to load templates from
encoding (str, optional): The encoding to use loading the template source
Defaults to "utf-8".
ext (str, optional): The extension to use when one isn't provided
Defaults to ".liquid".
"""

def __init__(
self,
search_package: Package,
encoding: str = "utf-8",
ext: str = ".liquid",
) -> None:
super().__init__()
self.search_package = search_package
self.encoding = encoding
self.ext = ext

def get_source(self, _: Environment, template_name: str) -> TemplateSource:
template_path = Path(template_name)
if ".." in template_path.parts:
raise TemplateNotFound(template_name)

if not template_path.suffix:
template_path = template_path.with_suffix(self.ext)
try:
resource_path = files(self.search_package).joinpath(template_path)
return TemplateSource(
source=resource_path.read_text(self.encoding),
filename=str(resource_path),
uptodate=lambda: True,
)
except (ModuleNotFoundError, FileNotFoundError):
raise TemplateNotFound(template_name)


@deprecated(
"CachedChoiceLoader is deprecated and scheduled for removal "
"in a future version. Use CachingChoiceLoader instead"
)
class CachedChoiceLoader(CachingLoaderMixin, ChoiceLoader):
"""A choice loader that caches parsed templates in memory
Args:
loaders: A list of loaders implementing `liquid.loaders.BaseLoader`
auto_reload (bool, optional): If `True`, automatically reload a cached template
if it has been updated. Defaults to True
cache_size (int, optional): The maximum number of templates to hold in the cache
before removing the least recently used template. Defaults to 300
namespace_key (str, optional): The name of a global render context variable or
loader keyword argument that resolves to the current loader "namespace" or
"scope". Defaults to ""
"""

def __init__(
self,
loaders: List[BaseLoader],
auto_reload: bool = True,
cache_size: int = 300,
namespace_key: str = "",
) -> None:
super().__init__(
auto_reload=auto_reload,
namespace_key=namespace_key,
cache_size=cache_size,
)
ChoiceLoader.__init__(self, loaders)
self.is_caching = cache_size > 0

def _check_cache(
self,
env: Environment, # noqa: ARG002
cache_key: str,
globals: TemplateNamespace, # noqa: A002
load_func: Callable[[], BoundTemplate],
) -> BoundTemplate:
if self.is_caching:
return super()._check_cache(env, cache_key, globals, load_func)
return load_func()


class TemplateSystemLoader(CachedChoiceLoader):
class TemplateSystemLoader(ChoiceLoader):
"""TemplateSystemLoader allows templates to be loaded from a primary and optionally secondary
location(s). This allows templates to include / render templates from the other location(s)
Template Names Resolution:
Any template (non json file) that is in a subdirectory will have _ prepended to the name
Ex: Section/Immunization -> Section/_Immunization
See ``CachedChoiceLoader`` for more information
See `ChoiceLoader` for more information
"""

def get_source(
Expand Down Expand Up @@ -170,6 +80,69 @@ def _resolve_template_name(self, template_name: str) -> str:
return str(template_path)


class CachingTemplateSystemLoader(CachingLoaderMixin, TemplateSystemLoader):
"""TemplateSystemLoader that caches parsed templates in memory.
See `TemplateSystemLoader` for more information
"""

def __init__(
self,
loaders: List[BaseLoader],
*,
auto_reload: bool = True,
namespace_key: str = "",
cache_size: int = 300,
) -> None:
super().__init__(
auto_reload=auto_reload,
namespace_key=namespace_key,
cache_size=cache_size,
)

TemplateSystemLoader.__init__(self, loaders)


def make_template_system_loader(
loader: BaseLoader,
*,
auto_reload: bool = True,
namespace_key: str = "",
cache_size: int = 300,
additional_loaders: Optional[Sequence[BaseLoader]] = None,
) -> BaseLoader:
"""make_template_system_loader A `TemplateSystemLoader` factory
Args:
loader (BaseLoader): The loader to use when loading the rendering temples
auto_reload (bool, optional): If `True`, loaders that have an `uptodate`
callable will reload template source data automatically. Defaults to False
namespace_key (str, optional): The name of a global render context variable or loader
keyword argument that resolves to the current loader "namespace" or
"scope". Defaults to ""
cache_size (int, optional): The capacity of the template cache in number of
templates. cache_size is None or less than 1 disables caching.
Defaults to 300
additional_loaders (Optional[Sequence[BaseLoader]], optional): The additional
loaders to use when a template is not found by the loader. Defaults to None
Returns:
BaseLoader: `CachingTemplateSystemLoader` if cache_size > 0 else `TemplateSystemLoader`
"""
loaders = [loader]
if additional_loaders:
loaders += additional_loaders
if cache_size > 0:
return CachingTemplateSystemLoader(
loaders=loaders,
auto_reload=auto_reload,
namespace_key=namespace_key,
cache_size=cache_size,
)

return TemplateSystemLoader(loaders=loaders)


def read_text(env: Environment, filename: str) -> str:
"""read_text Reads the text from the given filename using the Environment's
loader to retrieve the file's contents
Expand Down
21 changes: 10 additions & 11 deletions fhir_converter/renderers.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@

from frozendict import frozendict
from liquid import Environment
from liquid.loaders import BaseLoader
from liquid.loaders import BaseLoader, PackageLoader
from pyjson5 import encode_io
from pyjson5 import loads as json_loads

from fhir_converter.filters import all_filters, register_filters
from fhir_converter.hl7 import parse_fhir
from fhir_converter.loaders import ResourceLoader, TemplateSystemLoader, read_text
from fhir_converter.loaders import make_template_system_loader, read_text
from fhir_converter.tags import all_tags, register_tags
from fhir_converter.utils import (
del_empty_dirs_quietly,
Expand All @@ -52,13 +52,13 @@
RenderErrorHandler = Callable[[Exception], None]
"""Callable[[Exception], None]: Rendering error handling function"""

ccda_default_loader: Final[ResourceLoader] = ResourceLoader(
search_package="fhir_converter.templates.ccda"
ccda_default_loader: Final[PackageLoader] = PackageLoader(
package="fhir_converter.templates", package_path="ccda"
)
"""ResourceLoader: The default loader for the ccda templates"""

stu3_default_loader: Final[ResourceLoader] = ResourceLoader(
search_package="fhir_converter.templates.stu3"
stu3_default_loader: Final[PackageLoader] = PackageLoader(
package="fhir_converter.templates", package_path="stu3"
)
"""ResourceLoader: The default loader for the stu3 templates"""

Expand Down Expand Up @@ -292,15 +292,14 @@ def get_environment(
Returns:
Environment: The rendering environment
"""
loaders = [loader]
if additional_loaders:
loaders += additional_loaders
env = Environment(
loader=TemplateSystemLoader(
loaders,
loader=make_template_system_loader(
loader,
auto_reload=auto_reload,
cache_size=cache_size,
additional_loaders=additional_loaders,
),
cache_size=cache_size,
**kwargs,
)
register_filters(env, all_filters, replace=True)
Expand Down
40 changes: 19 additions & 21 deletions fhir_converter/templates/ccda/Resource/_AllergyIntolerance.liquid
Original file line number Diff line number Diff line change
Expand Up @@ -22,37 +22,35 @@
},
"reaction":
[
{% assign relationships = allergyEntry.entryRelationship | to_array -%}
{% for r in relationships -%}
{
{% if allergyEntry.participant.participantRole.playingEntity.code.translation -%}
"substance":{ {% include 'DataType/CodeableConcept' CodeableConcept: allergyEntry.participant.participantRole.playingEntity.code.translation -%} },
{% else -%}
"substance":{ {% include 'DataType/CodeableConcept' CodeableConcept: allergyEntry.participant.participantRole.playingEntity.code -%} },
{% endif -%}

{% assign templateIdString = r.observation.templateId | to_json_string -%}
{% if templateIdString contains '"2.16.840.1.113883.10.20.22.4.9"' -%}
"manifestation":
[
{% assign relationships = allergyEntry.entryRelationship | to_array -%}
{% for r in relationships -%}
{% assign templateIdString = r.observation.templateId | to_json_string -%}
{% if templateIdString contains '"2.16.840.1.113883.10.20.22.4.9"' -%}
"manifestation":
[
{% if r.observation.value.translation -%}
{ {% include 'DataType/CodeableConcept' CodeableConcept: r.observation.value.translation -%} },
{% else -%}
{ {% include 'DataType/CodeableConcept' CodeableConcept: r.observation.value -%} },
{% endif -%}
],
"onset": "{{ r.observation.effectiveTime.low.value | format_as_date_time }}",
{% endif -%}
{% if templateIdString contains '"2.16.840.1.113883.10.20.22.4.8"' -%}
{% if r.observation.value.translation -%}
{ {% include 'DataType/CodeableConcept' CodeableConcept: r.observation.value.translation -%} },
"severity":"{{ r.observation.value.translation.displayName | downcase | get_property: 'ValueSet/AllergySeverity' }}",
{% else -%}
{ {% include 'DataType/CodeableConcept' CodeableConcept: r.observation.value -%} },
"severity":"{{ r.observation.value.displayName | downcase | get_property: 'ValueSet/AllergySeverity' }}",
{% endif -%}
],
"onset": "{{ r.observation.effectiveTime.low.value | format_as_date_time }}",
{% endif -%}
{% if templateIdString contains '"2.16.840.1.113883.10.20.22.4.8"' -%}
{% if r.observation.value.translation -%}
"severity":"{{ r.observation.value.translation.displayName | downcase | get_property: 'ValueSet/AllergySeverity' }}",
{% else -%}
"severity":"{{ r.observation.value.displayName | downcase | get_property: 'ValueSet/AllergySeverity' }}",
{% endif -%}
{% endif -%}
},
{% endfor -%}

{% endfor -%}
}
],
"onsetDateTime":"{{ allergyEntry.effectiveTime.low.value | format_as_date_time }}",
},
Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"

[tool.poetry]
name = "python-fhir-converter"
version = "0.0.22"
version = "0.1.0"
authors = ["Chase Stewart <chase.a.stewart@outlook.com>"]
description = "Transformation utility to translate data formats into FHIR"
readme = "README.md"
Expand Down Expand Up @@ -38,7 +38,7 @@ packages = [

[tool.poetry.dependencies]
python = "^3.8.1"
python-liquid = "1.11.0"
python-liquid = "1.12.1"
xmltodict = "0.13.0"
pyjson5 = "1.6.5"
frozendict = "2.3.10"
Expand Down
Loading

0 comments on commit 7c6bed6

Please sign in to comment.