Skip to content

pyMetaModel API Documentation

metamodel

Created on 2022-11-23

@author: wf

Context

Bases: MetaModelElement

A Context groups some topics like a Namespace/Package. This class provides helper functions and constants to render a Context to corresponding wiki pages

Source code in meta/metamodel.py
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
class Context(MetaModelElement):
    """
    A Context groups some topics like a Namespace/Package.
    This class provides helper functions and constants to render a Context to corresponding wiki pages
    """

    def __init__(self):
        """
        constructor
        """
        MetaModelElement.__init__(self)
        self.topics = {}
        self.types = {}
        self.errors = []

    @classmethod
    def getSamples(cls):
        samples = [
            {
                "name": "MetaModel",
                "since": datetime.strptime("2015-01-23", "%Y-%m-%d"),
                "copyright": "2015-2024 BITPlan GmbH",
                "master": "http://smw-meta.bitplan.com",
            }
        ]
        return samples

    def error(self, msg):
        print(msg, file=sys.stderr)
        self.errors.append(msg)

    def lookupTopic(self, topic_name: str, purpose: str) -> "Topic":
        """
        lookup the given topic_name in my topics for the given purpose

        Args:
            topic_name (str): the name of the topic to lookup
            purpose (str): the purpose to do the lookup for

        Returns:
            Topic: the topic if found else None and an error is added to my errors
        """
        if topic_name in self.topics:
            return self.topics[topic_name]
        else:
            self.error(
                f"""topic {topic_name} not found in context {getattr(self,"name","?")} for {purpose}"""
            )
            return None

    def propertyAlreadyDeclared(
        self, prop_name: str, topic: "Topic", purpose: str
    ) -> bool:
        """
        check whether the given property or role name is already Declared

        Args:
            prop_name (str): the property to check for duplicates
            topic (Topic): the topic to check
            purpose (str): the purpose to be displayed in error messages

        Returns:
            bool: True if this prop_name has already been use
        """
        in_use = prop_name in topic.properties or prop_name in topic.targetTopicLinks
        if in_use:
            self.error(
                f"duplicate name {prop_name} in topic {topic.name} for {purpose}"
            )
        return in_use

    def link_source_with_target(
        self, tl: "TopicLink", source: "Topic", target: "Topic"
    ):
        """
        link the source with the target via the given topicLink

        Args:
            tl (TopicLink): the topicLink
            source (Topic): the source Topic
            target (Topic): the target Topic
        """
        ok = True
        ok = ok and not self.propertyAlreadyDeclared(
            tl.sourceRole, target, f"{tl.name}"
        )
        ok = ok and not self.propertyAlreadyDeclared(
            tl.targetRole, source, f"{tl.name}"
        )
        if ok:
            # n:m handling with two lists on each side
            source.sourceTopicLinks[tl.sourceRole] = tl
            source.targetTopicLinks[tl.targetRole] = tl

    def addLink(self, tl: "TopicLink"):
        """
        link source and target of the given topicLink

        Args:
            tl (TopicLink): the topicLink
        """
        tl.sourceTopic = self.lookupTopic(tl.source, f"topicLink {tl.name}")
        tl.targetTopic = self.lookupTopic(tl.target, f"topicLink {tl.name}")
        if tl.targetTopic is not None and tl.sourceTopic is not None:
            self.link_source_with_target(tl, tl.sourceTopic, tl.targetTopic)

    def addProperty(self, prop: "Property") -> bool:
        """
        add the given property to this context

        Args:
            prop (Property): the property to add

        Returns:
            bool: True if the property adding was successful
        """
        if not hasattr(prop, "topic"):
            self.error(f"prop  {prop} has no topic")
            return False
        topic_name = prop.topic
        if not hasattr(prop, "name"):
            self.error(f"prop {prop} has no name")
            return False
        if not hasattr(prop, "type"):
            prop.type = "Text"
        topic = self.lookupTopic(topic_name, f"property {prop.name}")
        if topic:
            topic.properties[prop.name] = prop
            return True

    def createProperty4TopicLink(self, tl: "TopicLink") -> "Property":
        """
        create a property for the given topic link

        Args:
            tl (TopicLink):  the topiclink to create a property for
        """
        # see https://wiki.bitplan.com/index.php/SiDIFTemplates#properties
        prop = Property()
        prop.name = tl.sourceRole
        prop.label = prop.name
        if hasattr(tl, "sourceDocumentation"):
            prop.documentation = tl.sourceDocumentation
        else:
            prop.documentation = f"{prop.name}"
        prop.topic = tl.target
        prop.type = "Page"
        prop.topicLink = tl
        prop.isLink = True
        return prop

    @classmethod
    def fromDictOfDicts(cls, did: dict) -> "Context":
        """
        fill me from the given dict of dicts

        Args:
            did (dict): the dict of dicts

        Returns:
            Context: the context read
        """
        context = None
        for key, record in did.items():
            isA = record["isA"]
            if isA == "Context":
                context = Context()
                context.fromDict(record)
            elif isA == "TopicLink":
                """
                # Event n : 1 City
                Event_in_City isA TopicLink
                "eventInCity" is name of it
                "city" is sourceRole of it
                false is sourceMultiple of it
                "City" is source of it
                "event" is targetRole of it
                true is targetMultiple of it
                "Event" is target of it
                """
                tl = TopicLink()
                tl.fromDict(record)
                context.addLink(tl)
            elif isA == "Property":
                prop = Property()
                prop.isLink = False
                prop.fromDict(record)
                context.addProperty(prop)
            else:  # isA == Topic or in declared topics
                topic = Topic()
                topic.fromDict(record)
                topic.sanitize()
                if context is None:
                    context = Context()
                    context.name = "GeneralContext"
                    context.since = "2022-11-26"
                    context.master = "http://master.bitplan.com"
                    context.error(f"topic {topic.name} has no defined context")
                if hasattr(topic, "name"):
                    context.topics[topic.name] = topic
                elif hasattr(topic, "type"):
                    context.types[topic.type]=topic
                else:
                    # potential foreign declaration
                    context.error(f"missing name for topic {topic} {key} - foreign declaration?")

        # link topic to concepts and add topicLinks
        for topic in context.topics.values():
            topic.setConceptProperty()
            for _tl_name, tl in topic.targetTopicLinks.items():
                prop = context.createProperty4TopicLink(tl)
                context.addProperty(prop)
                pass
        return context

    @classmethod
    def fromSiDIF_input(cls, sidif_input: str, debug: bool = False)->Tuple['Context',str,str]:
        """
        initialize me from the given SiDIF input which might be a file path
        or url

        Args:
            sidif_input (str): path to local file or URL
            debug (bool): if True swith debugging on

        Returns:
            Tuple[Context,str,str]: context, error and errorMessage
        """
        if sidif_input.startswith("http:") or sidif_input.startswith("https:"):
            url = sidif_input
            http = urllib3.PoolManager()
            response = http.request("GET", url)
            sidif = response.data.decode("utf-8")
        else:
            sidif_path = sidif_input
            with open(sidif_path, "r") as sidif_file:
                sidif = sidif_file.read()
        return Context.fromSiDIF(sidif, title=sidif_input, debug=debug)

    @classmethod
    def fromSiDIF(
        cls, sidif: str, title: str, depth: int = None, debug: bool = False
    ) -> typing.Tuple["Context", object, str]:
        """
        initialize me from the given SiDIF markup

        Args:
            sidif (str): the SiDIF markup to parse
            title (str): the title for the SiDIF
            depth (int): the explain depth to show for the errorMessage
            debug (bool): if True handle debugging

        Returns:
            Tuple[Context,str,str]: context, error and errorMessage

        """
        errMsg = None
        context = None
        sp = SiDIFParser(debug=debug)
        parsed, error = sp.parseText(sidif, title, depth=depth)
        if debug:
            sp.printResult(parsed)
        if error is None:
            dif = parsed[0]
            did = dif.toDictOfDicts()
            context = Context.fromDictOfDicts(did)
            context.dif = dif
            context.did = did
        else:
            errMsg = sp.errorMessage(title, error, depth=depth)
        return context, error, errMsg

    @classmethod
    def fromWikiContext(
        cls, mw_context: MediaWikiContext, depth: int = None, debug: bool = False
    ) -> Tuple["Context", Optional[Exception], Optional[str]]:
        """
        initialize me from the given MediaWiki Context

        Args:
            mw_context (MediaWikiContext): the Mediawiki context
            depth (int): the explain depth to show for the errorMessage
            debug (bool): if True handle debugging

        Return:
            tuple(Context,Exception,str): the metamodel and potential parsing errors as Exception and error Message
        """
        context = None
        error = None
        errMsg = None
        sidif = None
        if debug:
            print(f"reading sidif for {mw_context.context} from {mw_context.wikiId}")
        try:
            sidif = mw_context.read_sidif()
        except BaseException as ex:
            error = ex
            errMsg = str(ex)
        if sidif is not None:
            context, error, errMsg = cls.fromSiDIF(
                sidif=sidif, title=mw_context.wikiId, depth=depth, debug=debug
            )
        return context, error, errMsg

__init__()

constructor

Source code in meta/metamodel.py
59
60
61
62
63
64
65
66
def __init__(self):
    """
    constructor
    """
    MetaModelElement.__init__(self)
    self.topics = {}
    self.types = {}
    self.errors = []

link source and target of the given topicLink

Parameters:

Name Type Description Default
tl TopicLink

the topicLink

required
Source code in meta/metamodel.py
147
148
149
150
151
152
153
154
155
156
157
def addLink(self, tl: "TopicLink"):
    """
    link source and target of the given topicLink

    Args:
        tl (TopicLink): the topicLink
    """
    tl.sourceTopic = self.lookupTopic(tl.source, f"topicLink {tl.name}")
    tl.targetTopic = self.lookupTopic(tl.target, f"topicLink {tl.name}")
    if tl.targetTopic is not None and tl.sourceTopic is not None:
        self.link_source_with_target(tl, tl.sourceTopic, tl.targetTopic)

addProperty(prop)

add the given property to this context

Parameters:

Name Type Description Default
prop Property

the property to add

required

Returns:

Name Type Description
bool bool

True if the property adding was successful

Source code in meta/metamodel.py
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
def addProperty(self, prop: "Property") -> bool:
    """
    add the given property to this context

    Args:
        prop (Property): the property to add

    Returns:
        bool: True if the property adding was successful
    """
    if not hasattr(prop, "topic"):
        self.error(f"prop  {prop} has no topic")
        return False
    topic_name = prop.topic
    if not hasattr(prop, "name"):
        self.error(f"prop {prop} has no name")
        return False
    if not hasattr(prop, "type"):
        prop.type = "Text"
    topic = self.lookupTopic(topic_name, f"property {prop.name}")
    if topic:
        topic.properties[prop.name] = prop
        return True

create a property for the given topic link

Parameters:

Name Type Description Default
tl TopicLink

the topiclink to create a property for

required
Source code in meta/metamodel.py
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
def createProperty4TopicLink(self, tl: "TopicLink") -> "Property":
    """
    create a property for the given topic link

    Args:
        tl (TopicLink):  the topiclink to create a property for
    """
    # see https://wiki.bitplan.com/index.php/SiDIFTemplates#properties
    prop = Property()
    prop.name = tl.sourceRole
    prop.label = prop.name
    if hasattr(tl, "sourceDocumentation"):
        prop.documentation = tl.sourceDocumentation
    else:
        prop.documentation = f"{prop.name}"
    prop.topic = tl.target
    prop.type = "Page"
    prop.topicLink = tl
    prop.isLink = True
    return prop

fromDictOfDicts(did) classmethod

fill me from the given dict of dicts

Parameters:

Name Type Description Default
did dict

the dict of dicts

required

Returns:

Name Type Description
Context Context

the context read

Source code in meta/metamodel.py
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
@classmethod
def fromDictOfDicts(cls, did: dict) -> "Context":
    """
    fill me from the given dict of dicts

    Args:
        did (dict): the dict of dicts

    Returns:
        Context: the context read
    """
    context = None
    for key, record in did.items():
        isA = record["isA"]
        if isA == "Context":
            context = Context()
            context.fromDict(record)
        elif isA == "TopicLink":
            """
            # Event n : 1 City
            Event_in_City isA TopicLink
            "eventInCity" is name of it
            "city" is sourceRole of it
            false is sourceMultiple of it
            "City" is source of it
            "event" is targetRole of it
            true is targetMultiple of it
            "Event" is target of it
            """
            tl = TopicLink()
            tl.fromDict(record)
            context.addLink(tl)
        elif isA == "Property":
            prop = Property()
            prop.isLink = False
            prop.fromDict(record)
            context.addProperty(prop)
        else:  # isA == Topic or in declared topics
            topic = Topic()
            topic.fromDict(record)
            topic.sanitize()
            if context is None:
                context = Context()
                context.name = "GeneralContext"
                context.since = "2022-11-26"
                context.master = "http://master.bitplan.com"
                context.error(f"topic {topic.name} has no defined context")
            if hasattr(topic, "name"):
                context.topics[topic.name] = topic
            elif hasattr(topic, "type"):
                context.types[topic.type]=topic
            else:
                # potential foreign declaration
                context.error(f"missing name for topic {topic} {key} - foreign declaration?")

    # link topic to concepts and add topicLinks
    for topic in context.topics.values():
        topic.setConceptProperty()
        for _tl_name, tl in topic.targetTopicLinks.items():
            prop = context.createProperty4TopicLink(tl)
            context.addProperty(prop)
            pass
    return context

fromSiDIF(sidif, title, depth=None, debug=False) classmethod

initialize me from the given SiDIF markup

Parameters:

Name Type Description Default
sidif str

the SiDIF markup to parse

required
title str

the title for the SiDIF

required
depth int

the explain depth to show for the errorMessage

None
debug bool

if True handle debugging

False

Returns:

Type Description
Tuple[Context, object, str]

Tuple[Context,str,str]: context, error and errorMessage

Source code in meta/metamodel.py
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
@classmethod
def fromSiDIF(
    cls, sidif: str, title: str, depth: int = None, debug: bool = False
) -> typing.Tuple["Context", object, str]:
    """
    initialize me from the given SiDIF markup

    Args:
        sidif (str): the SiDIF markup to parse
        title (str): the title for the SiDIF
        depth (int): the explain depth to show for the errorMessage
        debug (bool): if True handle debugging

    Returns:
        Tuple[Context,str,str]: context, error and errorMessage

    """
    errMsg = None
    context = None
    sp = SiDIFParser(debug=debug)
    parsed, error = sp.parseText(sidif, title, depth=depth)
    if debug:
        sp.printResult(parsed)
    if error is None:
        dif = parsed[0]
        did = dif.toDictOfDicts()
        context = Context.fromDictOfDicts(did)
        context.dif = dif
        context.did = did
    else:
        errMsg = sp.errorMessage(title, error, depth=depth)
    return context, error, errMsg

fromSiDIF_input(sidif_input, debug=False) classmethod

initialize me from the given SiDIF input which might be a file path or url

Parameters:

Name Type Description Default
sidif_input str

path to local file or URL

required
debug bool

if True swith debugging on

False

Returns:

Type Description
Tuple[Context, str, str]

Tuple[Context,str,str]: context, error and errorMessage

Source code in meta/metamodel.py
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
@classmethod
def fromSiDIF_input(cls, sidif_input: str, debug: bool = False)->Tuple['Context',str,str]:
    """
    initialize me from the given SiDIF input which might be a file path
    or url

    Args:
        sidif_input (str): path to local file or URL
        debug (bool): if True swith debugging on

    Returns:
        Tuple[Context,str,str]: context, error and errorMessage
    """
    if sidif_input.startswith("http:") or sidif_input.startswith("https:"):
        url = sidif_input
        http = urllib3.PoolManager()
        response = http.request("GET", url)
        sidif = response.data.decode("utf-8")
    else:
        sidif_path = sidif_input
        with open(sidif_path, "r") as sidif_file:
            sidif = sidif_file.read()
    return Context.fromSiDIF(sidif, title=sidif_input, debug=debug)

fromWikiContext(mw_context, depth=None, debug=False) classmethod

initialize me from the given MediaWiki Context

Parameters:

Name Type Description Default
mw_context MediaWikiContext

the Mediawiki context

required
depth int

the explain depth to show for the errorMessage

None
debug bool

if True handle debugging

False
Return

tuple(Context,Exception,str): the metamodel and potential parsing errors as Exception and error Message

Source code in meta/metamodel.py
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
@classmethod
def fromWikiContext(
    cls, mw_context: MediaWikiContext, depth: int = None, debug: bool = False
) -> Tuple["Context", Optional[Exception], Optional[str]]:
    """
    initialize me from the given MediaWiki Context

    Args:
        mw_context (MediaWikiContext): the Mediawiki context
        depth (int): the explain depth to show for the errorMessage
        debug (bool): if True handle debugging

    Return:
        tuple(Context,Exception,str): the metamodel and potential parsing errors as Exception and error Message
    """
    context = None
    error = None
    errMsg = None
    sidif = None
    if debug:
        print(f"reading sidif for {mw_context.context} from {mw_context.wikiId}")
    try:
        sidif = mw_context.read_sidif()
    except BaseException as ex:
        error = ex
        errMsg = str(ex)
    if sidif is not None:
        context, error, errMsg = cls.fromSiDIF(
            sidif=sidif, title=mw_context.wikiId, depth=depth, debug=debug
        )
    return context, error, errMsg

link the source with the target via the given topicLink

Parameters:

Name Type Description Default
tl TopicLink

the topicLink

required
source Topic

the source Topic

required
target Topic

the target Topic

required
Source code in meta/metamodel.py
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
def link_source_with_target(
    self, tl: "TopicLink", source: "Topic", target: "Topic"
):
    """
    link the source with the target via the given topicLink

    Args:
        tl (TopicLink): the topicLink
        source (Topic): the source Topic
        target (Topic): the target Topic
    """
    ok = True
    ok = ok and not self.propertyAlreadyDeclared(
        tl.sourceRole, target, f"{tl.name}"
    )
    ok = ok and not self.propertyAlreadyDeclared(
        tl.targetRole, source, f"{tl.name}"
    )
    if ok:
        # n:m handling with two lists on each side
        source.sourceTopicLinks[tl.sourceRole] = tl
        source.targetTopicLinks[tl.targetRole] = tl

lookupTopic(topic_name, purpose)

lookup the given topic_name in my topics for the given purpose

Parameters:

Name Type Description Default
topic_name str

the name of the topic to lookup

required
purpose str

the purpose to do the lookup for

required

Returns:

Name Type Description
Topic Topic

the topic if found else None and an error is added to my errors

Source code in meta/metamodel.py
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
def lookupTopic(self, topic_name: str, purpose: str) -> "Topic":
    """
    lookup the given topic_name in my topics for the given purpose

    Args:
        topic_name (str): the name of the topic to lookup
        purpose (str): the purpose to do the lookup for

    Returns:
        Topic: the topic if found else None and an error is added to my errors
    """
    if topic_name in self.topics:
        return self.topics[topic_name]
    else:
        self.error(
            f"""topic {topic_name} not found in context {getattr(self,"name","?")} for {purpose}"""
        )
        return None

propertyAlreadyDeclared(prop_name, topic, purpose)

check whether the given property or role name is already Declared

Parameters:

Name Type Description Default
prop_name str

the property to check for duplicates

required
topic Topic

the topic to check

required
purpose str

the purpose to be displayed in error messages

required

Returns:

Name Type Description
bool bool

True if this prop_name has already been use

Source code in meta/metamodel.py
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
def propertyAlreadyDeclared(
    self, prop_name: str, topic: "Topic", purpose: str
) -> bool:
    """
    check whether the given property or role name is already Declared

    Args:
        prop_name (str): the property to check for duplicates
        topic (Topic): the topic to check
        purpose (str): the purpose to be displayed in error messages

    Returns:
        bool: True if this prop_name has already been use
    """
    in_use = prop_name in topic.properties or prop_name in topic.targetTopicLinks
    if in_use:
        self.error(
            f"duplicate name {prop_name} in topic {topic.name} for {purpose}"
        )
    return in_use

MetaModelElement

Bases: JSONAble

a generic MetaModelElement

to handle the technicalities of being a MetaModelElement so that derived MetaModelElements can focus on the MetaModel domain specific aspects

Source code in meta/metamodel.py
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
class MetaModelElement(JSONAble):
    """
    a generic MetaModelElement

    to handle the technicalities of being a MetaModelElement so that derived
    MetaModelElements can focus on the MetaModel domain specific aspects
    """

    def __init__(self):
        """
        construct me
        """
        self.__metamodel_props = {}
        cls = self.__class__
        if hasattr(cls, "getSamples"):
            for sample in cls.getSamples():
                for key in sample.keys():
                    if not key in self.__metamodel_props:
                        self.__metamodel_props[key] = key  # Property(self,key)

    def __str__(self):
        """
        get a string representation of me
        """
        text = self.__class__.__name__
        for prop_name in self.__metamodel_props.keys():
            if not isinstance(prop_name, str):
                pass
            elif hasattr(self, prop_name):
                value = getattr(self, prop_name)
                text += f"\n  {prop_name}={str(value)}"
        return text

__init__()

construct me

Source code in meta/metamodel.py
27
28
29
30
31
32
33
34
35
36
37
def __init__(self):
    """
    construct me
    """
    self.__metamodel_props = {}
    cls = self.__class__
    if hasattr(cls, "getSamples"):
        for sample in cls.getSamples():
            for key in sample.keys():
                if not key in self.__metamodel_props:
                    self.__metamodel_props[key] = key  # Property(self,key)

__str__()

get a string representation of me

Source code in meta/metamodel.py
39
40
41
42
43
44
45
46
47
48
49
50
def __str__(self):
    """
    get a string representation of me
    """
    text = self.__class__.__name__
    for prop_name in self.__metamodel_props.keys():
        if not isinstance(prop_name, str):
            pass
        elif hasattr(self, prop_name):
            value = getattr(self, prop_name)
            text += f"\n  {prop_name}={str(value)}"
    return text

Property

Bases: MetaModelElement

Provides helper functions and constants for properties

Source code in meta/metamodel.py
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
class Property(MetaModelElement):
    """
    Provides helper functions and constants for properties
    """

    def __init__(self):
        """
        constructor
        """
        MetaModelElement.__init__(self)

    @classmethod
    def getSamples(cls):
        samples = [
            {
                "name": "Title",
                "label": "Offical Name",
                "type": "Special:Types/Text",
                "index": 2,
                "sortPos": 2,
                "primaryKey": False,
                "mandatory": True,
                "namespace": "Test",
                "size": 25,
                "uploadable": False,
                "defaultValue": "Some Title",
                "inputType": "combobox",
                "allowedVaues": "Tile A, Title B",
                "documentation": " Documentation can contain\n line breaks",
                "values_from": "property=Title",
                "showInGrid": False,
                "isLink": False,
                "nullable": False,
                "topic": "Concept:Event",
            },
            {
                "name": "wikidataid",
                "type": "Special:Types/External identifier",
                "formatterURI": "https://www.wikidata.org/wiki/$1",
            },
            # Properties that are not included in the MetaModel
            {
                "placeholder": "e.g. SMWCon",
                "regexp": "NaturalNumber",
                "usedFor": "Concept:Event, Concept:Event series",
                "pageTitle": "Property:Title",
            },
        ]
        return samples

__init__()

constructor

Source code in meta/metamodel.py
553
554
555
556
557
def __init__(self):
    """
    constructor
    """
    MetaModelElement.__init__(self)

Topic

Bases: MetaModelElement

A Topic is a Concept/Class/Thing/Entity

Source code in meta/metamodel.py
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
class Topic(MetaModelElement):
    """
    A Topic is a Concept/Class/Thing/Entity
    """

    def __init__(self):
        """
        constructor
        """
        MetaModelElement.__init__(self)
        self._pluralName = None # Initialize with underscore to indicate a protected attribute

        self.properties = {}
        self.sourceTopicLinks = {}
        self.targetTopicLinks = {}

    def sanitize(self):
        """
        make sure my properties exist
        """
        doc=self.documentation if hasattr("self", "documentation") else "?"
        if not hasattr(self, "wikiDocumentation"):
            self.wikiDocumentation=doc
        if not hasattr(self,"defaultstoremode"):
            self.defaultstoremode="property"

    @classmethod
    def getSamples(cls):
        samples = [
            {
                "pageTitle": "Concept:Topic",
                "name": "Topic",
                "pluralName": "Topics",
                "icon": "File:Topic_icon.png",
                "iconUrl": "/images/a/ae/Index.png",
                "documentation": "A Topic is a Concept/Class/Thing/Entity",
                "wikiDocumentation": "A Topic is a Concept/Class/Thing/Entity",
                "context": "MetaModel",
                "listLimit": 7,
                "cargo": True,
            }
        ]
        return samples

    def setConceptProperty(self):
        """
        set my concept property to any primary key or mandatory property
        """
        self.conceptProperty = None
        for prop in self.properties.values():
            mandatory = False
            primaryKey = False
            if hasattr(prop, "mandatory"):
                mandatory = prop.mandatory
            if hasattr(prop, "primaryKey"):
                primaryKey = prop.primaryKey
            if mandatory or primaryKey:
                self.conceptProperty = prop
                break

    @property
    def pluralName(self)->str:
        """
        Getter for pluralName.

        Returns:
            str: The plural name of the topic.
        """
        # Default behavior if _pluralName is not explicitly set
        return self._pluralName if self._pluralName is not None else f"{self.name}s"

    @pluralName.setter
    def pluralName(self, value):
        """
        Setter for pluralName.

        Args:
            value (str): The plural name to be set for the topic.
        """
        self._pluralName = value

    def getPluralName(self) -> str:
        """
        get the plural name for this topic

        Returns:
            str: the pluralname e.g. "Topics" for "Topic" or "Status" for "Status" or
            "Entities" for "Entity"
        """
        return self.pluralName

    def getListLimit(self) -> int:
        """
        get the list limit for this topic
        """
        listLimit = getattr(self, "listLimit", 200)
        return listLimit

    def sortProperties(self) -> list:
        """
        get the properties that we should sort by

        Returns:
            list: a list of properties in sort order
        """
        prop_list = []
        for prop in self.properties.values():
            if hasattr(prop, "sortPos"):
                sortPos = prop.sortPos
                if sortPos:
                    prop_list.append(prop)
        prop_list = sorted(prop_list, key=lambda prop: prop.sortPos)
        return prop_list

    def propertiesByIndex(self) -> list:
        """
        return the properties by index

        Returns:
            list: the list of properties sorted by index
        """

        def index(prop: "Property") -> int:
            if hasattr(prop, "index"):
                return int(prop.index)
            else:
                return sys.maxsize

        prop_list = sorted(self.properties.values(), key=lambda prop: index(prop))
        return prop_list

    def askSort(self) -> str:
        """
        generate the sort clause for my SMW ask query

        Returns:
            str: the generated wiki markup
        """
        sort = ""
        order = ""
        delim = ""
        sortproperties = self.sortProperties()
        for prop in sortproperties:
            direction = (
                "ascending" if getattr(prop, "sortAscending", True) else "descending"
            )
            sort += delim + f"{self.name} {prop.name}"
            order += delim + direction
            delim = ","
            pass
        sortClause = f"|sort={sort}\n" if sort else ""
        orderClause = f"|order={order}\n" if order else ""
        markup = f"{sortClause}{orderClause}"
        return markup

    def askQuery(
        self,
        mainlabel: str = None,
        filterShowInGrid: bool = True,
        listLimit: int = None,
    ) -> str:
        """
        get the askQuery for the me topic

        Args:
            mainlabel (str): the mainlabel to use - topic.name as default
            filterShowInGrid (bool): if True include only properties with showInGrid not being false
            listLimit (int): the list limit to use
        Returns:
            str: the markup for the query
        """
        if listLimit is None:
            listLimit = self.getListLimit()
        if mainlabel is None:
            mainlabel = self.name
        markup = f"""{{{{#ask: [[Concept:{self.name}]]
|mainlabel={mainlabel}
"""
        for prop in self.properties.values():
            if filterShowInGrid and hasattr(prop, "showInGrid"):
                show = prop.showInGrid
            else:
                show = True
            if show:
                markup += f"|?{self.name} {prop.name} = {prop.name}\n"
        markup += f"|limit={listLimit}\n"
        markup += f"""{self.askSort()}}}}}"""
        return markup

pluralName: str property writable

Getter for pluralName.

Returns:

Name Type Description
str str

The plural name of the topic.

__init__()

constructor

Source code in meta/metamodel.py
363
364
365
366
367
368
369
370
371
372
def __init__(self):
    """
    constructor
    """
    MetaModelElement.__init__(self)
    self._pluralName = None # Initialize with underscore to indicate a protected attribute

    self.properties = {}
    self.sourceTopicLinks = {}
    self.targetTopicLinks = {}

askQuery(mainlabel=None, filterShowInGrid=True, listLimit=None)

get the askQuery for the me topic

Parameters:

Name Type Description Default
mainlabel str

the mainlabel to use - topic.name as default

None
filterShowInGrid bool

if True include only properties with showInGrid not being false

True
listLimit int

the list limit to use

None

Returns: str: the markup for the query

Source code in meta/metamodel.py
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
    def askQuery(
        self,
        mainlabel: str = None,
        filterShowInGrid: bool = True,
        listLimit: int = None,
    ) -> str:
        """
        get the askQuery for the me topic

        Args:
            mainlabel (str): the mainlabel to use - topic.name as default
            filterShowInGrid (bool): if True include only properties with showInGrid not being false
            listLimit (int): the list limit to use
        Returns:
            str: the markup for the query
        """
        if listLimit is None:
            listLimit = self.getListLimit()
        if mainlabel is None:
            mainlabel = self.name
        markup = f"""{{{{#ask: [[Concept:{self.name}]]
|mainlabel={mainlabel}
"""
        for prop in self.properties.values():
            if filterShowInGrid and hasattr(prop, "showInGrid"):
                show = prop.showInGrid
            else:
                show = True
            if show:
                markup += f"|?{self.name} {prop.name} = {prop.name}\n"
        markup += f"|limit={listLimit}\n"
        markup += f"""{self.askSort()}}}}}"""
        return markup

askSort()

generate the sort clause for my SMW ask query

Returns:

Name Type Description
str str

the generated wiki markup

Source code in meta/metamodel.py
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
def askSort(self) -> str:
    """
    generate the sort clause for my SMW ask query

    Returns:
        str: the generated wiki markup
    """
    sort = ""
    order = ""
    delim = ""
    sortproperties = self.sortProperties()
    for prop in sortproperties:
        direction = (
            "ascending" if getattr(prop, "sortAscending", True) else "descending"
        )
        sort += delim + f"{self.name} {prop.name}"
        order += delim + direction
        delim = ","
        pass
    sortClause = f"|sort={sort}\n" if sort else ""
    orderClause = f"|order={order}\n" if order else ""
    markup = f"{sortClause}{orderClause}"
    return markup

getListLimit()

get the list limit for this topic

Source code in meta/metamodel.py
449
450
451
452
453
454
def getListLimit(self) -> int:
    """
    get the list limit for this topic
    """
    listLimit = getattr(self, "listLimit", 200)
    return listLimit

getPluralName()

get the plural name for this topic

Returns:

Name Type Description
str str

the pluralname e.g. "Topics" for "Topic" or "Status" for "Status" or

str

"Entities" for "Entity"

Source code in meta/metamodel.py
439
440
441
442
443
444
445
446
447
def getPluralName(self) -> str:
    """
    get the plural name for this topic

    Returns:
        str: the pluralname e.g. "Topics" for "Topic" or "Status" for "Status" or
        "Entities" for "Entity"
    """
    return self.pluralName

propertiesByIndex()

return the properties by index

Returns:

Name Type Description
list list

the list of properties sorted by index

Source code in meta/metamodel.py
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
def propertiesByIndex(self) -> list:
    """
    return the properties by index

    Returns:
        list: the list of properties sorted by index
    """

    def index(prop: "Property") -> int:
        if hasattr(prop, "index"):
            return int(prop.index)
        else:
            return sys.maxsize

    prop_list = sorted(self.properties.values(), key=lambda prop: index(prop))
    return prop_list

sanitize()

make sure my properties exist

Source code in meta/metamodel.py
374
375
376
377
378
379
380
381
382
def sanitize(self):
    """
    make sure my properties exist
    """
    doc=self.documentation if hasattr("self", "documentation") else "?"
    if not hasattr(self, "wikiDocumentation"):
        self.wikiDocumentation=doc
    if not hasattr(self,"defaultstoremode"):
        self.defaultstoremode="property"

setConceptProperty()

set my concept property to any primary key or mandatory property

Source code in meta/metamodel.py
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
def setConceptProperty(self):
    """
    set my concept property to any primary key or mandatory property
    """
    self.conceptProperty = None
    for prop in self.properties.values():
        mandatory = False
        primaryKey = False
        if hasattr(prop, "mandatory"):
            mandatory = prop.mandatory
        if hasattr(prop, "primaryKey"):
            primaryKey = prop.primaryKey
        if mandatory or primaryKey:
            self.conceptProperty = prop
            break

sortProperties()

get the properties that we should sort by

Returns:

Name Type Description
list list

a list of properties in sort order

Source code in meta/metamodel.py
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
def sortProperties(self) -> list:
    """
    get the properties that we should sort by

    Returns:
        list: a list of properties in sort order
    """
    prop_list = []
    for prop in self.properties.values():
        if hasattr(prop, "sortPos"):
            sortPos = prop.sortPos
            if sortPos:
                prop_list.append(prop)
    prop_list = sorted(prop_list, key=lambda prop: prop.sortPos)
    return prop_list

Bases: MetaModelElement

A TopicLink links two Concepts/Topics

Source code in meta/metamodel.py
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
class TopicLink(MetaModelElement):
    """
    A TopicLink links two Concepts/Topics
    """

    @classmethod
    def getSamples(cls):
        samples = [
            {
                "isA": "TopicLink",
                "name": "containedProperties",
                "sourceRole": "topic",
                "sourceMultiple": False,
                "source": "Topic",
                "targetRole": "properties",
                "targetMultiple": True,
                "target": "Property",
            },
            {
                "isA": "TopicLink",
                "name": "containedTopics",
                "sourceRole": "context",
                "sourceMultiple": False,
                "source": "Context",
                "targetRole": "topics",
                "targetMultiple": True,
                "target": "Topic",
            },
            {
                "isA": "TopicLink",
                "name": "typeOfProperty",
                "sourceRole": "usedByProperties",
                "sourceMultiple": True,
                "source": "Property",
                "sourceDocumentation": "the properties having this type",
                "targetRole": "smw_type",
                "targetMultiple": False,
                "target": "SMW_Type",
                "masterDetail": False,
                "targetDocumentation": "the smw_type being used by this property",
            },
        ]
        return samples

metamodel_cmd

Created on 2023-02-19

@author: wf

MetaModelCmd

command line interface for metamodel handling

Source code in meta/metamodel_cmd.py
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
class MetaModelCmd:
    """
    command line interface for metamodel handling
    """

    def __init__(self, debug: bool = False):
        """
        constructor

        Args:
            debug (bool): if True switch debugging on
        """
        self.debug = debug
        self.error = None
        self.errMsg = None
        self.context = None

    @classmethod
    def getArgParser(cls):
        """
        get my argument parser
        """
        program_shortdesc = Version.description
        program_license = """%s

          Created by %s on %s.
          Copyright 2022-2024 Wolfgang Fahl. All rights reserved.

          Licensed under the Apache License 2.0
          http://www.apache.org/licenses/LICENSE-2.0

          Distributed on an "AS IS" basis without warranties
          or conditions of any kind, either express or implied.

        USAGE
        """ % (
            program_shortdesc,
            Version.authors,
            str(__date__),
        )
        parser = ArgumentParser(
            description=program_license, formatter_class=RawDescriptionHelpFormatter
        )
        parser.add_argument(
            "--about",
            help="show about info [default: %(default)s]",
            action="store_true",
        )
        parser.add_argument(
            "--context",
            default="MetaModel",
            help="context to read [default: %(default)s]",
        )
        parser.add_argument("--copyright", help="copyright message for diagrams")
        parser.add_argument(
            "-d", "--debug", dest="debug", action="store_true", help="show debug info"
        )
        parser.add_argument(
            "-wd",
            "--doc_width",
            help="Maximum width of documentation (default: %(default)s)",
            type=int,
            default=40,
        )
        parser.add_argument("-i", "--input", help="input file")

        parser.add_argument(
            "-l", "--linkml", action="store_true", help="create linkml yaml file"
        )
        parser.add_argument("-t", "--title", help="the title of a diagram")
        parser.add_argument(
            "-u", "--uml", action="store_true", help="create plantuml diagram"
        )
        parser.add_argument(
            "-at",
            "--withAt",
            action="store_true",
            help="generate with @startuml/@enduml bracket",
        )
        parser.add_argument(
            "--wikiId",
            default="wiki",
            help="id of the wiki to generate for [default: %(default)s]",
        )
        return parser

    def readContext(self, args):
        """
        read the context from the given args

        Args:
            args (Args): command line arguments
        """
        if args.input:
            result_tuple = Context.fromSiDIF_input(args.input, debug=args.debug)
            self.context, self.error, self.errMsg = result_tuple
        elif args.wikiId and args.context:
            self.wikiId = args.wikiId
            self.smwAccess = SMWAccess(args.wikiId)
            self.mw_contexts = self.smwAccess.getMwContexts()
            self.mw_context = self.mw_contexts.get(args.context, None)
            self.context, self.error, self.errMsg = Context.fromWikiContext(
                self.mw_context, debug=args.debug
            )

    def hasError(self):
        if self.error is not None:
            print(f"reading Context failed:\n{self.errMsg}\n{self.error}")
        return self.error

    def genUml(self, args:Namespace) -> str:
        """
        generate uml for the given context with the given name from the given wiki

        Args:
           args (Namespace): the command line arguments

        Returns:
            str: the PlantUml
        """
        self.readContext(args)
        uml=None
        if not self.hasError():
            uml = PlantUml(
                title=args.title,
                copyRight=args.copyright,
                withAt=args.withAt,
                doc_width=args.doc_width,
            )
            uml.fromDIF(self.context.dif)
        return uml


    def genLinkML(self, args:Namespace) -> str:
        """
        generate linkML yaml for the given command line arguments

        Args:
           args (Namespace): the command line arguments

        Returns:
            str: the uml markup
        """
        self.readContext(args)
        if not self.hasError():
            sidif2LinkML = SiDIF2LinkML(self.context)
            yaml_text = sidif2LinkML.asYaml()
            return yaml_text
        else:
            return None

__init__(debug=False)

constructor

Parameters:

Name Type Description Default
debug bool

if True switch debugging on

False
Source code in meta/metamodel_cmd.py
25
26
27
28
29
30
31
32
33
34
35
def __init__(self, debug: bool = False):
    """
    constructor

    Args:
        debug (bool): if True switch debugging on
    """
    self.debug = debug
    self.error = None
    self.errMsg = None
    self.context = None

genLinkML(args)

generate linkML yaml for the given command line arguments

Parameters:

Name Type Description Default
args Namespace

the command line arguments

required

Returns:

Name Type Description
str str

the uml markup

Source code in meta/metamodel_cmd.py
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
def genLinkML(self, args:Namespace) -> str:
    """
    generate linkML yaml for the given command line arguments

    Args:
       args (Namespace): the command line arguments

    Returns:
        str: the uml markup
    """
    self.readContext(args)
    if not self.hasError():
        sidif2LinkML = SiDIF2LinkML(self.context)
        yaml_text = sidif2LinkML.asYaml()
        return yaml_text
    else:
        return None

genUml(args)

generate uml for the given context with the given name from the given wiki

Parameters:

Name Type Description Default
args Namespace

the command line arguments

required

Returns:

Name Type Description
str str

the PlantUml

Source code in meta/metamodel_cmd.py
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
def genUml(self, args:Namespace) -> str:
    """
    generate uml for the given context with the given name from the given wiki

    Args:
       args (Namespace): the command line arguments

    Returns:
        str: the PlantUml
    """
    self.readContext(args)
    uml=None
    if not self.hasError():
        uml = PlantUml(
            title=args.title,
            copyRight=args.copyright,
            withAt=args.withAt,
            doc_width=args.doc_width,
        )
        uml.fromDIF(self.context.dif)
    return uml

getArgParser() classmethod

get my argument parser

Source code in meta/metamodel_cmd.py
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
@classmethod
def getArgParser(cls):
    """
    get my argument parser
    """
    program_shortdesc = Version.description
    program_license = """%s

      Created by %s on %s.
      Copyright 2022-2024 Wolfgang Fahl. All rights reserved.

      Licensed under the Apache License 2.0
      http://www.apache.org/licenses/LICENSE-2.0

      Distributed on an "AS IS" basis without warranties
      or conditions of any kind, either express or implied.

    USAGE
    """ % (
        program_shortdesc,
        Version.authors,
        str(__date__),
    )
    parser = ArgumentParser(
        description=program_license, formatter_class=RawDescriptionHelpFormatter
    )
    parser.add_argument(
        "--about",
        help="show about info [default: %(default)s]",
        action="store_true",
    )
    parser.add_argument(
        "--context",
        default="MetaModel",
        help="context to read [default: %(default)s]",
    )
    parser.add_argument("--copyright", help="copyright message for diagrams")
    parser.add_argument(
        "-d", "--debug", dest="debug", action="store_true", help="show debug info"
    )
    parser.add_argument(
        "-wd",
        "--doc_width",
        help="Maximum width of documentation (default: %(default)s)",
        type=int,
        default=40,
    )
    parser.add_argument("-i", "--input", help="input file")

    parser.add_argument(
        "-l", "--linkml", action="store_true", help="create linkml yaml file"
    )
    parser.add_argument("-t", "--title", help="the title of a diagram")
    parser.add_argument(
        "-u", "--uml", action="store_true", help="create plantuml diagram"
    )
    parser.add_argument(
        "-at",
        "--withAt",
        action="store_true",
        help="generate with @startuml/@enduml bracket",
    )
    parser.add_argument(
        "--wikiId",
        default="wiki",
        help="id of the wiki to generate for [default: %(default)s]",
    )
    return parser

readContext(args)

read the context from the given args

Parameters:

Name Type Description Default
args Args

command line arguments

required
Source code in meta/metamodel_cmd.py
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
def readContext(self, args):
    """
    read the context from the given args

    Args:
        args (Args): command line arguments
    """
    if args.input:
        result_tuple = Context.fromSiDIF_input(args.input, debug=args.debug)
        self.context, self.error, self.errMsg = result_tuple
    elif args.wikiId and args.context:
        self.wikiId = args.wikiId
        self.smwAccess = SMWAccess(args.wikiId)
        self.mw_contexts = self.smwAccess.getMwContexts()
        self.mw_context = self.mw_contexts.get(args.context, None)
        self.context, self.error, self.errMsg = Context.fromWikiContext(
            self.mw_context, debug=args.debug
        )

main(argv=None)

main program.

Source code in meta/metamodel_cmd.py
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
def main(argv=None):  # IGNORE:C0111
    """main program."""

    if argv is None:
        argv = sys.argv[1:]

    program_name = os.path.basename(__file__)
    program_version = f"v{__version__}"
    program_build_date = str(__updated__)
    program_version_message = f"{program_name} ({program_version},{program_build_date})"

    try:
        parser = MetaModelCmd.getArgParser()
        # Setup argument parser
        if len(argv) < 1:
            parser.print_usage()
            sys.exit(1)

        args = parser.parse_args(argv)
        mm_cmd = MetaModelCmd(debug=args.debug)
        if args.about:
            print(program_version_message)
            print(f"see {Version.doc_url}")
            webbrowser.open(Version.doc_url)
        if args.uml:
            uml = mm_cmd.genUml(args)
            print(uml)
        elif args.linkml:
            mm_cmd = MetaModelCmd(debug=args.debug)
            linkml_yaml = mm_cmd.genLinkML(args)
            print(linkml_yaml)

    except KeyboardInterrupt:
        ### handle keyboard interrupt ###
        return 1
    except Exception as e:
        if DEBUG:
            raise (e)
        indent = len(program_name) * " "
        sys.stderr.write(program_name + ": " + repr(e) + "\n")
        sys.stderr.write(indent + "  for help use --help")
        if args.debug:
            print(traceback.format_exc())
        return 2

mw

Created on 23.11.2022

@author: wf

MediaWikiContext dataclass

Class for keeping track of MediaWiki Context

Source code in meta/mw.py
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
@dataclass
class MediaWikiContext:
    """Class for keeping track of MediaWiki Context"""

    wikiId: str
    wiki_url: str
    context: str
    since: datetime.datetime
    master: str

    def sidif_url(self):
        """
        return the sidif url
        """
        url = f"{self.wiki_url}/index.php/{self.context}#sidif"
        return url

    def read_sidif(self) -> str:
        """

        Read the SiDIF for this Mediawiki context

        Returns:
            str: the SiDIF
        """
        sidif = None
        wikiusers = WikiUser.getWikiUsers(lenient=True)
        if self.wikiId in wikiusers:
            wikiUser = wikiusers[self.wikiId]
            self.wikiClient = WikiClient.ofWikiId(wikiUser.wikiId)
            if self.wikiClient.needsLogin():
                self.wikiClient.login()
            pageTitle = f"{self.context}"
            page = self.wikiClient.getPage(pageTitle)
            if page.exists:
                markup = page.text()
                mw_code = mwparserfromhell.parse(markup)
                sidif_sections = mw_code.get_sections(matches="sidif")
                if len(sidif_sections) != 1:
                    raise Exception(
                        f"found {len(sidif_sections)} sidif sections but expected exactly 1"
                    )
                for node in sidif_sections[0].filter_tags(matches="source"):
                    sidif = str(node.contents)
        return sidif

read_sidif()

Read the SiDIF for this Mediawiki context

Returns:

Name Type Description
str str

the SiDIF

Source code in meta/mw.py
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
def read_sidif(self) -> str:
    """

    Read the SiDIF for this Mediawiki context

    Returns:
        str: the SiDIF
    """
    sidif = None
    wikiusers = WikiUser.getWikiUsers(lenient=True)
    if self.wikiId in wikiusers:
        wikiUser = wikiusers[self.wikiId]
        self.wikiClient = WikiClient.ofWikiId(wikiUser.wikiId)
        if self.wikiClient.needsLogin():
            self.wikiClient.login()
        pageTitle = f"{self.context}"
        page = self.wikiClient.getPage(pageTitle)
        if page.exists:
            markup = page.text()
            mw_code = mwparserfromhell.parse(markup)
            sidif_sections = mw_code.get_sections(matches="sidif")
            if len(sidif_sections) != 1:
                raise Exception(
                    f"found {len(sidif_sections)} sidif sections but expected exactly 1"
                )
            for node in sidif_sections[0].filter_tags(matches="source"):
                sidif = str(node.contents)
    return sidif

sidif_url()

return the sidif url

Source code in meta/mw.py
26
27
28
29
30
31
def sidif_url(self):
    """
    return the sidif url
    """
    url = f"{self.wiki_url}/index.php/{self.context}#sidif"
    return url

SMWAccess

access to semantic MediaWiki

Source code in meta/mw.py
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
class SMWAccess:
    """
    access to semantic MediaWiki
    """

    def __init__(self, wikiId: str = "wiki", debug: bool = False):
        """
        constructor
        """
        self.wikiId = wikiId
        self.smw = self.getSMW(wikiId)
        self.debug = debug
        self.url = f"{self.wikiClient.wikiUser.getWikiUrl()}"

    def getSMW(self, wikiId: str):
        """
        get the semantic mediawiki access
        """
        self.wikiClient = WikiClient.ofWikiId(wikiId)
        if self.wikiClient.needsLogin():
            self.wikiClient.login()
        smw = SMWClient(self.wikiClient.getSite())
        return smw

    def getMwContexts(self):
        """
        get the contexts
        """
        ask = """{{#ask: [[Concept:Context]]
|mainlabel=Context
| ?Context name = name
| ?Context since = since
| ?Context master = master

|sort=Context name
|order=ascending
}}"""
        mw_contexts = {}
        context_records = self.smw.query(ask, "list of contexts")
        for context_name, context_record in context_records.items():
            if self.debug:
                print(context_record)
            mw_contexts[context_name] = MediaWikiContext(
                self.wikiId,
                self.url,
                context_name,
                context_record["since"],
                context_record["master"],
            )
        return mw_contexts

__init__(wikiId='wiki', debug=False)

constructor

Source code in meta/mw.py
68
69
70
71
72
73
74
75
def __init__(self, wikiId: str = "wiki", debug: bool = False):
    """
    constructor
    """
    self.wikiId = wikiId
    self.smw = self.getSMW(wikiId)
    self.debug = debug
    self.url = f"{self.wikiClient.wikiUser.getWikiUrl()}"

getMwContexts()

get the contexts

Source code in meta/mw.py
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
    def getMwContexts(self):
        """
        get the contexts
        """
        ask = """{{#ask: [[Concept:Context]]
|mainlabel=Context
| ?Context name = name
| ?Context since = since
| ?Context master = master

|sort=Context name
|order=ascending
}}"""
        mw_contexts = {}
        context_records = self.smw.query(ask, "list of contexts")
        for context_name, context_record in context_records.items():
            if self.debug:
                print(context_record)
            mw_contexts[context_name] = MediaWikiContext(
                self.wikiId,
                self.url,
                context_name,
                context_record["since"],
                context_record["master"],
            )
        return mw_contexts

getSMW(wikiId)

get the semantic mediawiki access

Source code in meta/mw.py
77
78
79
80
81
82
83
84
85
def getSMW(self, wikiId: str):
    """
    get the semantic mediawiki access
    """
    self.wikiClient = WikiClient.ofWikiId(wikiId)
    if self.wikiClient.needsLogin():
        self.wikiClient.login()
    smw = SMWClient(self.wikiClient.getSite())
    return smw

profiler

Created on 2022-11-18

@author: wf

Profiler

simple profiler

Source code in meta/profiler.py
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class Profiler:
    """
    simple profiler
    """

    def __init__(self, msg, profile=True):
        """
        construct me with the given msg and profile active flag

        Args:
            msg (str): the message to show if profiling is active
            profile (bool): True if messages should be shown
        """
        self.msg = msg
        self.profile = profile
        self.starttime = time.time()
        if profile:
            print(f"Starting {msg} ...")

    def time(self, extraMsg=""):
        """
        time the action and print if profile is active
        """
        elapsed = time.time() - self.starttime
        if self.profile:
            print(f"{self.msg}{extraMsg} took {elapsed:5.1f} s")
        return elapsed

__init__(msg, profile=True)

construct me with the given msg and profile active flag

Parameters:

Name Type Description Default
msg str

the message to show if profiling is active

required
profile bool

True if messages should be shown

True
Source code in meta/profiler.py
15
16
17
18
19
20
21
22
23
24
25
26
27
def __init__(self, msg, profile=True):
    """
    construct me with the given msg and profile active flag

    Args:
        msg (str): the message to show if profiling is active
        profile (bool): True if messages should be shown
    """
    self.msg = msg
    self.profile = profile
    self.starttime = time.time()
    if profile:
        print(f"Starting {msg} ...")

time(extraMsg='')

time the action and print if profile is active

Source code in meta/profiler.py
29
30
31
32
33
34
35
36
def time(self, extraMsg=""):
    """
    time the action and print if profile is active
    """
    elapsed = time.time() - self.starttime
    if self.profile:
        print(f"{self.msg}{extraMsg} took {elapsed:5.1f} s")
    return elapsed

sidif2linkml

Created on 2023-02-20

@author: wf

SiDIF2LinkML

converter between SiDIF and LINKML

Source code in meta/sidif2linkml.py
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
class SiDIF2LinkML:
    """
    converter between SiDIF and LINKML
    """

    def __init__(self, context: Context):
        self.context = context

    def asYaml(self, common_property_slots: bool = True, delim="_") -> str:
        """
        convert my context

        Args:
            common_property_slots (bool): if True reuse slots

        Returns:
            str: the yaml markup

        """
        context = self.context
        sb = SchemaBuilder(id=context.name, name=context.name)
        # https://linkml.io/linkml-model/docs/SchemaDefinition/
        sb.add_defaults()
        sd = sb.schema
        sv = SchemaView(sd)
        if hasattr(context, "copyright"):
            copyright_str = f" copyright {context.copyright}"
        else:
            copyright_str = ""
        if hasattr(context, "master"):
            master = context.master
        else:
            master = "http://example.com"
        uri = f"{master}/{context.name}"
        for topic in self.context.topics.values():
            cd = ClassDefinition(name=topic.name)
            cd.description = topic.documentation
            sv.add_class(cd)
            for prop in topic.properties.values():
                slot = None
                if common_property_slots:
                    qname = prop.name
                    if prop.name in sd.slots:
                        slot = sd.slots[prop.name]
                        slot.description += "," + prop.documentation
                else:
                    qname = f"{topic.name}{delim}{prop.name}"
                if slot is None:
                    slot = SlotDefinition(name=qname)
                    if hasattr(prop, "documentation"):
                        slot.description = prop.documentation
                    typesMap = {
                        "Boolean": "boolean",
                        "Code": "string",  # @TODO will need special type
                        "Date": "date",
                        "Text": "string",
                        "URL": "uri",
                        "Page": "string",  # @TODO will need own type
                        "Number": "double",  # @TODO will need special type handling
                    }
                    if prop.isLink:
                        # 1:1 and 1:n handling
                        slot.range = prop.topicLink.source
                    else:
                        if prop.type in typesMap:
                            slot.range = typesMap[prop.type]
                        else:
                            slot.range = "string"
                    sv.add_slot(slot)
                cd.attributes[qname] = slot
                pass

            pass

        lml_gen = LinkmlGenerator(schema=sd, format="yaml")
        yaml_text = lml_gen.serialize()
        return yaml_text

asYaml(common_property_slots=True, delim='_')

convert my context

Parameters:

Name Type Description Default
common_property_slots bool

if True reuse slots

True

Returns:

Name Type Description
str str

the yaml markup

Source code in meta/sidif2linkml.py
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
def asYaml(self, common_property_slots: bool = True, delim="_") -> str:
    """
    convert my context

    Args:
        common_property_slots (bool): if True reuse slots

    Returns:
        str: the yaml markup

    """
    context = self.context
    sb = SchemaBuilder(id=context.name, name=context.name)
    # https://linkml.io/linkml-model/docs/SchemaDefinition/
    sb.add_defaults()
    sd = sb.schema
    sv = SchemaView(sd)
    if hasattr(context, "copyright"):
        copyright_str = f" copyright {context.copyright}"
    else:
        copyright_str = ""
    if hasattr(context, "master"):
        master = context.master
    else:
        master = "http://example.com"
    uri = f"{master}/{context.name}"
    for topic in self.context.topics.values():
        cd = ClassDefinition(name=topic.name)
        cd.description = topic.documentation
        sv.add_class(cd)
        for prop in topic.properties.values():
            slot = None
            if common_property_slots:
                qname = prop.name
                if prop.name in sd.slots:
                    slot = sd.slots[prop.name]
                    slot.description += "," + prop.documentation
            else:
                qname = f"{topic.name}{delim}{prop.name}"
            if slot is None:
                slot = SlotDefinition(name=qname)
                if hasattr(prop, "documentation"):
                    slot.description = prop.documentation
                typesMap = {
                    "Boolean": "boolean",
                    "Code": "string",  # @TODO will need special type
                    "Date": "date",
                    "Text": "string",
                    "URL": "uri",
                    "Page": "string",  # @TODO will need own type
                    "Number": "double",  # @TODO will need special type handling
                }
                if prop.isLink:
                    # 1:1 and 1:n handling
                    slot.range = prop.topicLink.source
                else:
                    if prop.type in typesMap:
                        slot.range = typesMap[prop.type]
                    else:
                        slot.range = "string"
                sv.add_slot(slot)
            cd.attributes[qname] = slot
            pass

        pass

    lml_gen = LinkmlGenerator(schema=sd, format="yaml")
    yaml_text = lml_gen.serialize()
    return yaml_text

smw_type

SMW_Type dataclass

an SMW_Type is a data type which determines the possible values for that type e.g. a Boolean can hold true/false values while a Number can hold 3.1459 or 20. A Page can hold the name of a Wiki page see https://semantic-mediawiki.org/wiki/Help:List_of_datatypes

Source code in meta/smw_type.py
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
@dataclass
class SMW_Type:
    """
    an SMW_Type is a data type which determines the possible values for that type e.g. a Boolean can hold true/false values while a Number can hold 3.1459 or 20. A Page can hold the name of a Wiki page see https://semantic-mediawiki.org/wiki/Help:List_of_datatypes
    """

    pageTitle: str
    type: Optional[
        str
    ]  # The Semantic MediaWiki type  without the prefix e.g. Text, Number, Boolean
    documentation: Optional[str]  # The documentation of this Semantic Media Wiki type
    id: Optional[str]  # SMW internal id of the type
    helppage: Optional[str]  # The url of the 'official' documentation page of this type
    typepage: Optional[
        str
    ]  # The Semantic Media Wiki Special page for this specific type e.g. Special:Types/Text, Special:Types/Boolean, Special:Types/Date, Special:Types/Number, Special:Types/Page
    javaType: Optional[str]  # Java mapping of this type

    @classmethod
    def askQuery(cls)->str:
        """
        get the ask Query for SMW_Type

        Returns:
            str: the mediawiki markup for the ask query
        """
        ask = """{{#ask: [[Concept:SMW_Type]]
|mainlabel=pageTitle
|?SMW_Type type = type
|?SMW_Type documentation = documentation
|?SMW_Type id = id
|?SMW_Type helppage = helppage
|?SMW_Type typepage = typepage
|?SMW_Type javaType = javaType
| limit=200
}}"""
        return ask

    @classmethod
    def fromDict(cls, data: dict)->'SMW_Type':
        """
        create a SMW_Type from the given dict

        Args:
            data (dict): the dict to create the SMW_Type from

        Returns:
            SMW_Type: the freshly created SMW_Type
        """
        smw_type = dacite.from_dict(data_class=cls, data=data)
        return smw_type

askQuery() classmethod

get the ask Query for SMW_Type

Returns:

Name Type Description
str str

the mediawiki markup for the ask query

Source code in meta/smw_type.py
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
    @classmethod
    def askQuery(cls)->str:
        """
        get the ask Query for SMW_Type

        Returns:
            str: the mediawiki markup for the ask query
        """
        ask = """{{#ask: [[Concept:SMW_Type]]
|mainlabel=pageTitle
|?SMW_Type type = type
|?SMW_Type documentation = documentation
|?SMW_Type id = id
|?SMW_Type helppage = helppage
|?SMW_Type typepage = typepage
|?SMW_Type javaType = javaType
| limit=200
}}"""
        return ask

fromDict(data) classmethod

create a SMW_Type from the given dict

Parameters:

Name Type Description Default
data dict

the dict to create the SMW_Type from

required

Returns:

Name Type Description
SMW_Type SMW_Type

the freshly created SMW_Type

Source code in meta/smw_type.py
45
46
47
48
49
50
51
52
53
54
55
56
57
@classmethod
def fromDict(cls, data: dict)->'SMW_Type':
    """
    create a SMW_Type from the given dict

    Args:
        data (dict): the dict to create the SMW_Type from

    Returns:
        SMW_Type: the freshly created SMW_Type
    """
    smw_type = dacite.from_dict(data_class=cls, data=data)
    return smw_type

uml

Created on 2020-11-12

@author: wf

PlantUml

Bases: object

Plant UML support

Source code in meta/uml.py
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
class PlantUml(object):
    """
    Plant UML support
    """

    # redundant to skinparams in pylodstorage.uml
    skinparams = """
' BITPlan Corporate identity skin params
' Copyright (c) 2015-2024 BITPlan GmbH
' see http://wiki.bitplan.com/PlantUmlSkinParams#BITPlanCI
' skinparams generated by com.bitplan.restmodelmanager
skinparam note {
  BackGroundColor #FFFFFF
  FontSize 12
  ArrowColor #FF8000
  BorderColor #FF8000
  FontColor black
  FontName Technical
}
skinparam component {
  BackGroundColor #FFFFFF
  FontSize 12
  ArrowColor #FF8000
  BorderColor #FF8000
  FontColor black
  FontName Technical
}
skinparam package {
  BackGroundColor #FFFFFF
  FontSize 12
  ArrowColor #FF8000
  BorderColor #FF8000
  FontColor black
  FontName Technical
}
skinparam usecase {
  BackGroundColor #FFFFFF
  FontSize 12
  ArrowColor #FF8000
  BorderColor #FF8000
  FontColor black
  FontName Technical
}
skinparam activity {
  BackGroundColor #FFFFFF
  FontSize 12
  ArrowColor #FF8000
  BorderColor #FF8000
  FontColor black
  FontName Technical
}
skinparam classAttribute {
  BackGroundColor #FFFFFF
  FontSize 12
  ArrowColor #FF8000
  BorderColor #FF8000
  FontColor black
  FontName Technical
}
skinparam interface {
  BackGroundColor #FFFFFF
  FontSize 12
  ArrowColor #FF8000
  BorderColor #FF8000
  FontColor black
  FontName Technical
}
skinparam class {
  BackGroundColor #FFFFFF
  FontSize 12
  ArrowColor #FF8000
  BorderColor #FF8000
  FontColor black
  FontName Technical
}
skinparam object {
  BackGroundColor #FFFFFF
  FontSize 12
  ArrowColor #FF8000
  BorderColor #FF8000
  FontColor black
  FontName Technical
}
hide Circle
' end of skinparams '
"""

    def __init__(
        self,
        copyRight=None,
        title=None,
        debug=False,
        withSkin: bool = True,
        withAt: bool = False,
        doc_width: int = 40,
    ):
        """
        Constructor
        """
        self.debug = debug
        self.withSkin = withSkin
        self.withAt = withAt
        self.uml = ""
        self.title = title
        self.copyRight = copyRight
        self.doc_width = doc_width

    def __str__(self):
        return self.atIt(self.uml)

    def atIt(self, markup: str):
        """
        Args:
            markup (str): the markup
        Returns:
            str: markup with @startuml and @enduml
        """
        if not self.withAt:
            return markup
        at_markup = f"""@startuml
{markup}
@enduml"""
        return at_markup

    def multi_line_doc(self, doc: str, width: int) -> str:
        """
        Returns the given documentation as a multiline string with a limited length per line.
        Lines are broken at whitespace.

        Args:
            doc (str): the documentation to wrap
            width (int): The maximum length of each line.
        Returns:
            str: the  Multiline string.
        """
        text = "\n".join(textwrap.wrap(doc, width=width))
        return text

    def asUmlDict(self, dif):
        """
        return the given DataInterchange as an UML Dict
        """
        uml = {"packages": {}, "topiclinks": {}}
        classKey = None
        classes = {}
        for _i, triple in enumerate(dif.triples):
            if self.debug:
                print(triple)
            if triple.p == "isA":
                itkey = triple.s
                if triple.o == "Context":
                    packageKey = itkey
                    packages = uml["packages"]
                    packages[itkey] = {"classes": {}}
                    it = packages[itkey]
                elif triple.o == "TopicLink":
                    links = uml["topiclinks"]
                    links[itkey] = {}
                    it = links[itkey]
                elif triple.o == "Property":
                    propKey = itkey
                    if not classKey:
                        print(f"invalid property {propKey} declared out of class scope")
                        continue
                    properties = classes[classKey]["properties"]
                    properties[propKey] = {}
                    it = properties[propKey]
                else:
                    isA = triple.o
                    classKey = itkey
                    classes = packages[packageKey]["classes"]
                    classes[itkey] = {"isA": isA, "properties": {}}
                    it = classes[itkey]
            elif triple.o == "it":
                if triple.p == "addsTo":
                    # @TODO might note be redundant but
                    # declaring a default
                    # redundant forward declaration
                    pass
                elif triple.p == "context":
                    parentKey = triple.s
                    packages[parentKey]["classes"][classKey] = classes[classKey]
                    pass
                elif triple.p == "topic":
                    parentKey = triple.s
                    classes[parentKey]["properties"][propKey] = properties[propKey]
                    pass
                else:
                    it[triple.p] = triple.s
        return uml

    def fromDIF(self, dif:DataInterchange) -> str:
        """
        create a UML from a Data Interchange

        Args:
            dif (DataInterchange): - the data interchange

        Returns:
            str: the planuml markup

        """
        umlDict = self.asUmlDict(dif)
        self.uml = self.fromUmlDict(umlDict)
        return

    def fromUmlDict(self, umlDict: Dict) -> str:
        """
        convert the given umlDict Dict to a plantuml string

        Args:
            umlDict (Dict): the dictionary of packages,classes and properties

        Returns:
            str: the planuml markup
        """
        uml = ""
        if self.title is not None:
            if self.copyRight is None:
                copyRight = ""
            else:
                copyRight = "\n%s" % self.copyRight
            uml += f"title\n{self.title}{copyRight}\nend title\n"
        packages = umlDict["packages"]
        for packageKey in packages.keys():
            package = packages[packageKey]
            uml += f"package {package['name']} {{\n"
            for classKey in package["classes"]:
                uclass = package["classes"][classKey]
                className = uclass.get("name", classKey)
                isA = uclass["isA"]
                if isA != "Topic":
                    extends = f"extends {isA} "
                else:
                    extends = ""
                uml += f"  class {className} {extends}{{\n"
                for propKey in uclass["properties"]:
                    prop = uclass["properties"][propKey]
                    uml += f"    {prop['name']}:{prop['type']}\n"
                uml += "  }\n"
                if "documentation" in uclass:
                    doc = uclass["documentation"]
                    doc = self.multi_line_doc(doc, self.doc_width)
                    uml += f"""Note top of {className}
{doc}
End note
"""
            uml += "}\n"

        links = umlDict["topiclinks"]
        for linkKey in links.keys():
            link = links[linkKey]
            sourceMany = "*" if link["sourceMultiple"] else "1"
            targetMany = "*" if link["targetMultiple"] else "1"
            sourceRole = link["sourceRole"] if "sourceRole" in link else ""
            targetRole = link["targetRole"] if "targetRole" in link else ""
            source = link["source"]
            target = link["target"]
            uml += f'{source} "{sourceRole} {sourceMany}" -- "{targetRole} {targetMany}" {target}\n'

        if self.withSkin:
            uml += PlantUml.skinparams
        return uml

__init__(copyRight=None, title=None, debug=False, withSkin=True, withAt=False, doc_width=40)

Constructor

Source code in meta/uml.py
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
def __init__(
    self,
    copyRight=None,
    title=None,
    debug=False,
    withSkin: bool = True,
    withAt: bool = False,
    doc_width: int = 40,
):
    """
    Constructor
    """
    self.debug = debug
    self.withSkin = withSkin
    self.withAt = withAt
    self.uml = ""
    self.title = title
    self.copyRight = copyRight
    self.doc_width = doc_width

asUmlDict(dif)

return the given DataInterchange as an UML Dict

Source code in meta/uml.py
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
def asUmlDict(self, dif):
    """
    return the given DataInterchange as an UML Dict
    """
    uml = {"packages": {}, "topiclinks": {}}
    classKey = None
    classes = {}
    for _i, triple in enumerate(dif.triples):
        if self.debug:
            print(triple)
        if triple.p == "isA":
            itkey = triple.s
            if triple.o == "Context":
                packageKey = itkey
                packages = uml["packages"]
                packages[itkey] = {"classes": {}}
                it = packages[itkey]
            elif triple.o == "TopicLink":
                links = uml["topiclinks"]
                links[itkey] = {}
                it = links[itkey]
            elif triple.o == "Property":
                propKey = itkey
                if not classKey:
                    print(f"invalid property {propKey} declared out of class scope")
                    continue
                properties = classes[classKey]["properties"]
                properties[propKey] = {}
                it = properties[propKey]
            else:
                isA = triple.o
                classKey = itkey
                classes = packages[packageKey]["classes"]
                classes[itkey] = {"isA": isA, "properties": {}}
                it = classes[itkey]
        elif triple.o == "it":
            if triple.p == "addsTo":
                # @TODO might note be redundant but
                # declaring a default
                # redundant forward declaration
                pass
            elif triple.p == "context":
                parentKey = triple.s
                packages[parentKey]["classes"][classKey] = classes[classKey]
                pass
            elif triple.p == "topic":
                parentKey = triple.s
                classes[parentKey]["properties"][propKey] = properties[propKey]
                pass
            else:
                it[triple.p] = triple.s
    return uml

atIt(markup)

Parameters:

Name Type Description Default
markup str

the markup

required

Returns: str: markup with @startuml and @enduml

Source code in meta/uml.py
122
123
124
125
126
127
128
129
130
131
132
133
134
    def atIt(self, markup: str):
        """
        Args:
            markup (str): the markup
        Returns:
            str: markup with @startuml and @enduml
        """
        if not self.withAt:
            return markup
        at_markup = f"""@startuml
{markup}
@enduml"""
        return at_markup

fromDIF(dif)

create a UML from a Data Interchange

Parameters:

Name Type Description Default
dif DataInterchange
  • the data interchange
required

Returns:

Name Type Description
str str

the planuml markup

Source code in meta/uml.py
203
204
205
206
207
208
209
210
211
212
213
214
215
216
def fromDIF(self, dif:DataInterchange) -> str:
    """
    create a UML from a Data Interchange

    Args:
        dif (DataInterchange): - the data interchange

    Returns:
        str: the planuml markup

    """
    umlDict = self.asUmlDict(dif)
    self.uml = self.fromUmlDict(umlDict)
    return

fromUmlDict(umlDict)

convert the given umlDict Dict to a plantuml string

Parameters:

Name Type Description Default
umlDict Dict

the dictionary of packages,classes and properties

required

Returns:

Name Type Description
str str

the planuml markup

Source code in meta/uml.py
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
    def fromUmlDict(self, umlDict: Dict) -> str:
        """
        convert the given umlDict Dict to a plantuml string

        Args:
            umlDict (Dict): the dictionary of packages,classes and properties

        Returns:
            str: the planuml markup
        """
        uml = ""
        if self.title is not None:
            if self.copyRight is None:
                copyRight = ""
            else:
                copyRight = "\n%s" % self.copyRight
            uml += f"title\n{self.title}{copyRight}\nend title\n"
        packages = umlDict["packages"]
        for packageKey in packages.keys():
            package = packages[packageKey]
            uml += f"package {package['name']} {{\n"
            for classKey in package["classes"]:
                uclass = package["classes"][classKey]
                className = uclass.get("name", classKey)
                isA = uclass["isA"]
                if isA != "Topic":
                    extends = f"extends {isA} "
                else:
                    extends = ""
                uml += f"  class {className} {extends}{{\n"
                for propKey in uclass["properties"]:
                    prop = uclass["properties"][propKey]
                    uml += f"    {prop['name']}:{prop['type']}\n"
                uml += "  }\n"
                if "documentation" in uclass:
                    doc = uclass["documentation"]
                    doc = self.multi_line_doc(doc, self.doc_width)
                    uml += f"""Note top of {className}
{doc}
End note
"""
            uml += "}\n"

        links = umlDict["topiclinks"]
        for linkKey in links.keys():
            link = links[linkKey]
            sourceMany = "*" if link["sourceMultiple"] else "1"
            targetMany = "*" if link["targetMultiple"] else "1"
            sourceRole = link["sourceRole"] if "sourceRole" in link else ""
            targetRole = link["targetRole"] if "targetRole" in link else ""
            source = link["source"]
            target = link["target"]
            uml += f'{source} "{sourceRole} {sourceMany}" -- "{targetRole} {targetMany}" {target}\n'

        if self.withSkin:
            uml += PlantUml.skinparams
        return uml

multi_line_doc(doc, width)

Returns the given documentation as a multiline string with a limited length per line. Lines are broken at whitespace.

Parameters:

Name Type Description Default
doc str

the documentation to wrap

required
width int

The maximum length of each line.

required

Returns: str: the Multiline string.

Source code in meta/uml.py
136
137
138
139
140
141
142
143
144
145
146
147
148
def multi_line_doc(self, doc: str, width: int) -> str:
    """
    Returns the given documentation as a multiline string with a limited length per line.
    Lines are broken at whitespace.

    Args:
        doc (str): the documentation to wrap
        width (int): The maximum length of each line.
    Returns:
        str: the  Multiline string.
    """
    text = "\n".join(textwrap.wrap(doc, width=width))
    return text

version

Created on 2023-02-19

@author: wf

Version

Bases: object

Version handling for pyMetaModel

Source code in meta/version.py
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Version(object):
    """
    Version handling for pyMetaModel
    """

    name = "pyMetaModel"
    description = "pyMetaModel: MetaModel for Knowledge Graphs"
    version = meta.__version__
    date = "2022-11-30"
    updated = "2024-08-02"
    authors = "Wolfgang Fahl"
    doc_url = "https://wiki.bitplan.com/index.php/pyMetaModel"
    chat_url = "https://github.com/WolfgangFahl/pyMetaModel/discussions"
    cm_url = "https://github.com/WolfgangFahl/pyMetaModel"
    license = f"""Copyright 2022-2024 contributors. All rights reserved.
  Licensed under the Apache License 2.0
  http://www.apache.org/licenses/LICENSE-2.0
  Distributed on an "AS IS" basis without warranties
  or conditions of any kind, either express or implied."""
    longDescription = f"""{name} version {version}
{description}
  Created by {authors} on {date} last updated {updated}"""