stormlibpp 0.2.0__tar.gz → 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: stormlibpp
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: The stormlibpp Python package
5
5
  Author: John Gorman
6
6
  License: MIT License
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "stormlibpp"
7
- version = "0.2.0"
7
+ version = "0.3.0"
8
8
  description = "The stormlibpp Python package"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -29,9 +29,10 @@ are briefly described below:
29
29
 
30
30
  """
31
31
 
32
- __version__ = "0.2.0"
32
+ __version__ = "0.3.0"
33
33
 
34
34
  from . import errors
35
35
  from . import utils
36
+ from .node import StormNode
36
37
  from .stormpkg import StormPkg
37
38
  from .telepath import (genDefaultTelepathRetn, TelepathRetn)
@@ -0,0 +1,197 @@
1
+ """Work with storm:node objects in Python."""
2
+
3
+
4
+ from types import SimpleNamespace
5
+ from typing import Any, Self, TypedDict
6
+
7
+
8
+ NodeForm = str
9
+ """A valid syn:form value."""
10
+
11
+
12
+ class NodeVals(TypedDict):
13
+ """The items a Node can have in the second part of packed NodeTuple."""
14
+
15
+ iden: str
16
+ nodedata: dict[str, Any]
17
+ props: dict[str, Any]
18
+ tags: dict[str, Any]
19
+ tagprops: dict[str, Any]
20
+
21
+
22
+ NodeDef = tuple[NodeForm, str]
23
+ """A nodes definition, the node's form and primary value."""
24
+
25
+ NodeTuple = tuple[NodeDef, NodeVals]
26
+ """A packed node, this is what get's sent from Storm to a Telepath endpoint.
27
+
28
+ Or what get's returned from a Cortex's Python APIs.
29
+ """
30
+
31
+
32
+ class ItemContainer:
33
+ """Hold key/value items in a SimpleNamespace.
34
+
35
+ Keys must be strings.
36
+
37
+ Supports ``obj['item']`` (and ``obj['item'] = 'val'``) notation.
38
+
39
+ When "getting" an item with ``__getitem__``, that doesn't exist in the
40
+ container, ``None`` is returned instead of raising an exception.
41
+
42
+ Calling ``dir`` on this object will only return names for items this object
43
+ "holds" instead of all parts of this object.
44
+
45
+ Supports checking if an item is "held" via the ``in`` operator.
46
+
47
+ Supports iteration of items in the SimpleNamespace. Each iteration returns
48
+ a tuple of the item name followed by the item value.
49
+
50
+ Implements only magic methods, and all of them operate on the underlying
51
+ SimpleNamespace. To convert this object to a ``dict`` of the items it holds,
52
+ call ``vars`` on an instance.
53
+ """
54
+
55
+ def __init__(self, /, **kwargs) -> None:
56
+ self.__ns = SimpleNamespace(**kwargs)
57
+
58
+ def __contains__(self, __name: str) -> bool:
59
+ return __name in dir(self)
60
+
61
+ @property
62
+ def __dict__(self):
63
+ return self.__ns.__dict__
64
+
65
+ def __dir__(self) -> list[str]:
66
+ return [val for val in dir(self.__ns) if not val.startswith("_")]
67
+
68
+ def __iter__(self) -> Any:
69
+ for item in dir(self):
70
+ yield (item, self[item])
71
+
72
+ def __getitem__(self, __name: str) -> Any:
73
+ return getattr(self.__ns, __name, None)
74
+
75
+ def __repr__(self) -> str:
76
+ return repr(self.__ns).replace("namespace", self.__class__.__name__)
77
+
78
+ def __setitem__(self, __name: str, __value: str) -> None:
79
+ setattr(self.__ns, __name, __value)
80
+
81
+
82
+ class Props(ItemContainer):
83
+ """Properties of a StormNode."""
84
+
85
+
86
+ class Tags(ItemContainer):
87
+ """Tags of a StormNode."""
88
+
89
+
90
+
91
+ class StormNode:
92
+ """A storm:node Storm object that is in a Python runtime.
93
+
94
+ It supports "unpacking" the node into this object, edits to the object, and
95
+ repacking of the node to be sent back to Storm.
96
+
97
+ This node can be created purely from Python code rather than a packed
98
+ ``storm:node`` object. Use the ``__init__`` directly instead of ``unpack``.
99
+ This allows the creation of a new node in Python that can be passed back to
100
+ Storm.
101
+
102
+ The intent is to represent an object that can be read from and written back
103
+ to a Cortex. It isn't meant to provide the same API that a ``storm:node``
104
+ has in Storm.
105
+ """
106
+
107
+ def __init__(
108
+ self,
109
+ form: NodeForm,
110
+ value: Any,
111
+ /,
112
+ iden: str = "",
113
+ tags: dict = {},
114
+ props: dict = {},
115
+ nodedata: dict = {},
116
+ tagprops: dict = {},
117
+ **kwargs,
118
+ ) -> None:
119
+
120
+ self._form = form
121
+ self._value = value
122
+
123
+ self._tags = Props(**tags)
124
+ self._props = Tags(**props)
125
+
126
+ self.iden = iden
127
+ self.nodedata = nodedata
128
+ self.tagprops = tagprops
129
+
130
+ # Not really sure if this is needed, but saving it for now.
131
+ self._xtras = kwargs
132
+
133
+ @property
134
+ def form(self) -> str:
135
+ """The form of this Node."""
136
+
137
+ return self._form
138
+
139
+ @property
140
+ def value(self) -> str:
141
+ """The value of this Node."""
142
+
143
+ return self._value
144
+
145
+ @property
146
+ def tags(self):
147
+ """The tags of this Node."""
148
+
149
+ return self._tags
150
+
151
+ @property
152
+ def props(self):
153
+ """The properties of this Node."""
154
+
155
+ return self._props
156
+
157
+ @property
158
+ def nodevals(self):
159
+ """The NodeVals dict this Node would have in a NodeTuple."""
160
+
161
+ return NodeVals(
162
+ iden=self.iden,
163
+ tagprops=self.tagprops,
164
+ nodedata=self.nodedata,
165
+ props=vars(self.props),
166
+ tags=vars(self.tags),
167
+ )
168
+
169
+ @classmethod
170
+ def unpack(cls, packedtup: NodeTuple) -> Self:
171
+ form, name = packedtup[0]
172
+ vals = NodeVals(**packedtup[1])
173
+ return cls(
174
+ form,
175
+ name,
176
+ **vals
177
+ )
178
+
179
+ def pack(self) -> NodeTuple:
180
+ return ((self.form, self.value), self.nodevals)
181
+
182
+ def addTag(self, tag, timestamps: tuple = (None, None)) -> None:
183
+ if tag in self._tags and timestamps != (None, None) and self._tags[tag] != timestamps:
184
+ self._tags[tag] = timestamps
185
+ elif tag not in self._tags:
186
+ self._tags[tag] = timestamps
187
+
188
+ def addTags(self, tags, timestamps: tuple = (None, None)) -> None:
189
+ for tag in tags:
190
+ self.addTag(tag, timestamps=timestamps)
191
+
192
+ def addProp(self, propname: str, propval: Any) -> None:
193
+ self._props[propname] = propval
194
+
195
+ def addProps(self, props: dict[str, Any]) -> None:
196
+ for propname, propval in props.items():
197
+ self._props[propname] = propval
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: stormlibpp
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: The stormlibpp Python package
5
5
  Author: John Gorman
6
6
  License: MIT License
@@ -12,6 +12,7 @@ src/stormlibpp.egg-info/SOURCES.txt
12
12
  src/stormlibpp.egg-info/dependency_links.txt
13
13
  src/stormlibpp.egg-info/requires.txt
14
14
  src/stormlibpp.egg-info/top_level.txt
15
+ tests/test_node.py
15
16
  tests/test_stormpkg.py
16
17
  tests/test_telepath.py
17
18
  tests/test_utils.py
@@ -0,0 +1,43 @@
1
+ """Tests for stormlibpp.node."""
2
+
3
+
4
+ import stormlibpp
5
+
6
+
7
+ PACKED_NODE = (('it:app:yara:rule', '5d0292d99a7da59a3a119e193f140143'), {'iden': '57ce731580ed6976b608e87dfc7f2ece81790f2cb8cdaf958013c738b28cd56e', 'tags': {'test': (None, None), 'test.tag': (None, None)}, 'props': {'.created': 1687720565352, 'text': 'rule dummy { condition: true }', 'name': 'dummy', 'enabled': 1}, 'tagprops': {}, 'nodedata': {}})
8
+
9
+
10
+ class TestNode:
11
+
12
+ def test_packing(self):
13
+ node = stormlibpp.StormNode.unpack(PACKED_NODE)
14
+ assert node.pack() == PACKED_NODE
15
+
16
+ def test_add_tags(self):
17
+ tags = ["#test.othertag", "#atag"]
18
+ node = stormlibpp.StormNode.unpack(PACKED_NODE)
19
+ node.addTags(tags)
20
+
21
+ for tag in tags:
22
+ assert tag in node.tags
23
+
24
+ assert dir(node.tags) == [item[0] for item in iter(node.tags)]
25
+
26
+ def test_add_props(self):
27
+ props = {'prop': 'test', 'other': 'value'}
28
+ node = stormlibpp.StormNode.unpack(PACKED_NODE)
29
+ node.addProps(props)
30
+
31
+ for prop in props:
32
+ assert prop in node.props
33
+
34
+ for propname, propval in node.props:
35
+ if propname in props:
36
+ assert props[propname] == propval
37
+
38
+ def test_values(self):
39
+ node = stormlibpp.StormNode.unpack(PACKED_NODE)
40
+
41
+ assert node.form == PACKED_NODE[0][0]
42
+ assert node.value == PACKED_NODE[0][1]
43
+ assert node.nodevals == PACKED_NODE[1]
@@ -1,13 +0,0 @@
1
- class PackedNode:
2
- """A "packed" storm:node Storm object that is in a Python runtime.
3
-
4
- It supports "unpacking" the node into this object, edits to the object, and
5
- repacking of the node to be sent back to Storm.
6
-
7
- This node can be created purely from Python code rather than a packed
8
- ``storm:node`` object. Use the ``__init__`` directly instead of ``unpack``.
9
- This allows the creation of a new node in Python that can be passed back to
10
- Storm.
11
- """
12
-
13
- raise NotImplementedError
File without changes
File without changes
File without changes