Skip to content

Commit 9be959b

Browse files
committed
Merge branch 'main' into issue_5_message_thread
2 parents 6ad4bc7 + edfeea1 commit 9be959b

File tree

12 files changed

+201
-22
lines changed

12 files changed

+201
-22
lines changed

.github/workflows/ci.yml

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,17 @@ jobs:
2424

2525
- name: Install Dependencies
2626
run: |
27-
pip -q install poetry pre-commit
27+
pip -q install poetry pre-commit rdflib
2828
poetry install
2929
3030
- name: Pre-commit Checks
3131
run: pre-commit run --all-files
3232

33+
- name: Type Checking
34+
run: poetry run mypy case_mapping example.py tests
35+
3336
- name: Unit Tests
34-
run: poetry run pytest
37+
run: poetry run pytest --doctest-modules
3538

3639
- name: Run Example
3740
run: poetry run python example.py > case.jsonld
@@ -44,6 +47,11 @@ jobs:
4447
case-version: "case-1.3.0"
4548
extension-filter: "jsonld"
4649

50+
- name: Convert example
51+
run: |
52+
rdfpipe --output-format turtle case.jsonld > case.ttl
53+
test 0 -eq $(grep 'file:' case.ttl | wc -l) || (echo "ERROR:ci.yml:Some graph IRIs do not supply a resolving prefix. Look for the string 'file:///' in the file case.ttl (created by running 'make check') to see these instances." >&2 ; exit 1)
54+
4755
# Always build the package as a sanity check to ensure no issues with the build system
4856
# exist as part of the CI process
4957
- name: Build Package

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,4 @@ __pycache__/
1919
# Build Artifacts
2020
build/
2121
dist/
22+
case.ttl

Makefile

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ all: \
3434
.venv-pre-commit/var/.pre-commit-built.log \
3535
case.jsonld
3636

37+
.PHONY: \
38+
check-mypy
39+
3740
# This virtual environment is meant to be built once and then persist, even through 'make clean'.
3841
# If a recipe is written to remove this flag file, it should first run `pre-commit uninstall`.
3942
.venv-pre-commit/var/.pre-commit-built.log:
@@ -81,7 +84,35 @@ case.jsonld: \
8184
_$@
8285
mv _$@ $@
8386

87+
case.ttl: \
88+
case.jsonld
89+
source venv/bin/activate \
90+
&& rdfpipe \
91+
--output-format turtle \
92+
$< \
93+
> _$@
94+
source venv/bin/activate \
95+
&& case_validate \
96+
_$@
97+
@# In instances where graph nodes omit use of a prefix, a 'default' prefix-base is used that incorporates the local directory as a file URL. This is likely to be an undesired behavior, so for the generated example JSON-LD in the top source directory, check that "file:///" doesn't start any of the graph individuals' IRIs.
98+
test \
99+
0 \
100+
-eq \
101+
$$(grep 'file:' _$@ | wc -l) \
102+
|| ( echo "ERROR:Makefile:Some graph IRIs do not supply a resolving prefix. Look for the string 'file:///' in the file _$@ to see these instances." >&2 ; exit 1)
103+
mv _$@ $@
104+
84105
check: \
85-
all
106+
all \
107+
check-mypy \
108+
case.ttl
109+
source venv/bin/activate \
110+
&& poetry run pytest --doctest-modules
111+
112+
check-mypy: \
113+
.venv.done.log
86114
source venv/bin/activate \
87-
&& poetry run pytest
115+
&& mypy \
116+
case_mapping \
117+
example.py \
118+
tests

case.jsonld

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
"uco-tool": "https://ontology.unifiedcyberontology.org/uco/tool/",
1616
"uco-types": "https://ontology.unifiedcyberontology.org/uco/types/",
1717
"uco-vocabulary": "https://ontology.unifiedcyberontology.org/uco/vocabulary/",
18-
"xsd": "http://www.w3.org/2001/XMLSchema#"
18+
"xsd": "http://www.w3.org/2001/XMLSchema#",
19+
"kb": "http://example.org/kb/"
1920
},
2021
"@type": "uco-core:Bundle",
2122
"uco-core:description": "An Example Case File",

case_mapping/base.py

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import json
22
from datetime import datetime
3+
from typing import Any
34

45
from cdo_local_uuid import local_uuid
56

@@ -20,8 +21,35 @@ def wrapper(self, *args, **kwargs):
2021

2122

2223
class FacetEntity(dict):
23-
def __init__(self):
24-
self["@id"] = "kb:" + str(local_uuid())
24+
def __init__(
25+
self,
26+
*args: Any,
27+
prefix_iri: str = "http://example.org/kb/",
28+
prefix_label: str = "kb",
29+
**kwargs: Any,
30+
) -> None:
31+
"""
32+
:param prefix_iri: The IRI to be concatenated with the prefixed name's local part in order to form the node's absolute IRI.
33+
:param prefix_label: The prefix separated from the compact IRI's local name by a colon.
34+
35+
References
36+
==========
37+
38+
.. [#turtle_prefixed_name] https://www.w3.org/TR/turtle/#prefixed-name
39+
40+
Examples
41+
========
42+
43+
When instantiating a ``FacetEntity``, the JSON dictionary's ``@id`` key will start with the object's ``prefix_label``.
44+
45+
>>> x = FacetEntity()
46+
>>> assert x["@id"][0:3] == "kb:"
47+
>>> y = FacetEntity(prefix_label="ex")
48+
>>> assert y["@id"][0:3] == "ex:"
49+
"""
50+
self.prefix_iri = prefix_iri
51+
self.prefix_label = prefix_label
52+
self["@id"] = prefix_label + ":" + str(local_uuid())
2553

2654
def __str__(self):
2755
return json.dumps(self, indent=4)
@@ -159,6 +187,9 @@ def __handle_var_type_errors(var_name, var_val, expected_type):
159187

160188

161189
class ObjectEntity(FacetEntity):
190+
def __init__(self, *args: Any, **kwargs: Any) -> None:
191+
super().__init__(*args, **kwargs)
192+
162193
@unpack_args_array
163194
def append_facets(self, *args):
164195
"""

case_mapping/directory.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1+
from .base import FacetEntity
12
from .case import *
23
from .uco import *
34

4-
submodules = [v for k, v in globals().items() if k[:2] != "__"]
5+
submodules = [v for k, v in globals().items() if k[:2] != "__" and k != "FacetEntity"]
56

6-
directory = dict()
7+
directory: dict[str, type[FacetEntity]] = dict()
78
for submodule in submodules:
89
directory |= submodule.directory

case_mapping/uco/core.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from typing import Any
2+
13
from ..base import ObjectEntity, unpack_args_array
24

35

@@ -8,12 +10,14 @@ def __init__(
810
uco_core_name=None,
911
spec_version=None,
1012
description=None,
11-
):
13+
*args: Any,
14+
**kwargs: Any,
15+
) -> None:
1216
"""
1317
The main CASE Object for representing a case and its activities and objects.
1418
"""
15-
super().__init__()
16-
self.build = []
19+
super().__init__(*args, **kwargs)
20+
self.build = [] # type: ignore
1721
self["@context"] = {
1822
"@vocab": "http://caseontology.org/core#",
1923
"case-investigation": "https://ontology.caseontology.org/case/investigation/",
@@ -31,6 +35,17 @@ def __init__(
3135
"uco-vocabulary": "https://ontology.unifiedcyberontology.org/uco/vocabulary/",
3236
"xsd": "http://www.w3.org/2001/XMLSchema#",
3337
}
38+
39+
# Assign caller-selectible prefix label and IRI, after checking
40+
# for conflicts with hard-coded prefixes.
41+
# https://www.w3.org/TR/turtle/#prefixed-name
42+
if self.prefix_label in self["@context"]:
43+
raise ValueError(
44+
"Requested prefix label already in use in hard-coded dictionary: '%s'. Please revise caller to use another label."
45+
% self.prefix_label
46+
)
47+
self["@context"][self.prefix_label] = self.prefix_iri
48+
3449
self["@type"] = "uco-core:Bundle"
3550
self._str_vars(
3651
**{

case_mapping/uco/observable.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,10 @@ def __init__(
269269
}
270270

271271
if hash_method is not None or hash_value is not None or hash_value != "-":
272-
data = {"@id": "kb:" + str(local_uuid()), "@type": "uco-types:Hash"}
272+
data = {
273+
"@id": self.prefix_label + ":" + str(local_uuid()),
274+
"@type": "uco-types:Hash",
275+
}
273276
if hash_method is not None:
274277
data["uco-types:hashMethod"] = {
275278
"@type": "uco-vocabulary:HashNameVocab",
@@ -416,7 +419,7 @@ def __init__(self, browser=None, history_entries=None):
416419
self["uco-observable:urlHistoryEntry"] = []
417420
for entry in history_entries:
418421
history_entry = {}
419-
history_entry["@id"] = "kb:" + str(local_uuid())
422+
history_entry["@id"] = self.prefix_label + ":" + local_uuid()
420423
history_entry["@type"] = "uco-observable:URLHistoryEntry"
421424
for key, var in entry.items():
422425
if key in keys_str:
@@ -858,14 +861,14 @@ def __init__(self, **kwargs):
858861
self["@type"] = "uco-observable:EXIFFacet"
859862

860863
self["uco-observable:exifData"] = {
861-
"@id": "kb:" + str(local_uuid()),
864+
"@id": self.prefix_label + ":" + str(local_uuid()),
862865
"@type": "uco-types:ControlledDictionary",
863866
"uco-types:entry": [],
864867
}
865868
for k, v in kwargs.items():
866869
if v not in ["", " "]:
867870
item = {
868-
"@id": "kb:" + str(local_uuid()),
871+
"@id": self.prefix_label + ":" + str(local_uuid()),
869872
"@type": "uco-types:ControlledDictionaryEntry",
870873
"uco-types:key": k,
871874
"uco-types:value": v,
@@ -1357,7 +1360,7 @@ def __init__(
13571360
self._node_reference_vars(**{"uco-observable:participant": participants})
13581361

13591362
self["uco-observable:messageThread"] = {
1360-
"@id": "kb:" + str(local_uuid()),
1363+
"@id": self.prefix_label + ":" + local_uuid(),
13611364
"@type": "uco-types:Thread",
13621365
}
13631366
self["uco-observable:messageThread"]["co:size"] = {

case_mapping/uco/tool.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
1+
from typing import Optional
2+
13
from ..base import ObjectEntity
24

35

46
class Tool(ObjectEntity):
57
def __init__(
6-
self, tool_name=None, tool_version=None, tool_type=None, tool_creator=None
8+
self,
9+
tool_name=None,
10+
tool_version=None,
11+
tool_type=None,
12+
tool_creator: Optional[ObjectEntity] = None,
713
):
814
"""
915
The Uco tool is a way to define the specifics of a tool used in an investigation

example.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import cdo_local_uuid
44

5-
from case_mapping import case, uco
5+
from case_mapping import base, case, uco
66

77
# This is part of enabling non-random UUIDs for the demonstration
88
# output. The other part is handled at call time, and can be seen in
@@ -28,7 +28,7 @@ def _next_timestamp() -> datetime:
2828

2929
# Generate a case bundle and list to hold investigation items
3030
bundle = uco.core.Bundle(description="An Example Case File")
31-
investigation_items = []
31+
investigation_items: list[base.FacetEntity] = []
3232

3333
###################################
3434
# An item to be added to the case #

poetry.lock

Lines changed: 82 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@ python = "^3.9"
2323
pytz = "^2023.3.post1"
2424

2525
[tool.poetry.dev-dependencies]
26+
mypy = "^1"
2627
pytest = "^7.4.2"
28+
types-pytz = "^2024"
2729

2830
[build-system]
2931
requires = ["poetry-core"]

0 commit comments

Comments
 (0)