personal_knowledge_library 3.0.0__py3-none-any.whl

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.

Potentially problematic release.


This version of personal_knowledge_library might be problematic. Click here for more details.

Files changed (42) hide show
  1. knowledge/__init__.py +91 -0
  2. knowledge/base/__init__.py +22 -0
  3. knowledge/base/access.py +167 -0
  4. knowledge/base/entity.py +267 -0
  5. knowledge/base/language.py +27 -0
  6. knowledge/base/ontology.py +2734 -0
  7. knowledge/base/search.py +473 -0
  8. knowledge/base/tenant.py +192 -0
  9. knowledge/nel/__init__.py +11 -0
  10. knowledge/nel/base.py +495 -0
  11. knowledge/nel/engine.py +123 -0
  12. knowledge/ontomapping/__init__.py +667 -0
  13. knowledge/ontomapping/manager.py +320 -0
  14. knowledge/public/__init__.py +27 -0
  15. knowledge/public/cache.py +115 -0
  16. knowledge/public/helper.py +373 -0
  17. knowledge/public/relations.py +128 -0
  18. knowledge/public/wikidata.py +1324 -0
  19. knowledge/services/__init__.py +128 -0
  20. knowledge/services/asyncio/__init__.py +7 -0
  21. knowledge/services/asyncio/base.py +458 -0
  22. knowledge/services/asyncio/graph.py +1420 -0
  23. knowledge/services/asyncio/group.py +450 -0
  24. knowledge/services/asyncio/search.py +439 -0
  25. knowledge/services/asyncio/users.py +270 -0
  26. knowledge/services/base.py +533 -0
  27. knowledge/services/graph.py +1897 -0
  28. knowledge/services/group.py +819 -0
  29. knowledge/services/helper.py +142 -0
  30. knowledge/services/ontology.py +1234 -0
  31. knowledge/services/search.py +488 -0
  32. knowledge/services/session.py +444 -0
  33. knowledge/services/tenant.py +281 -0
  34. knowledge/services/users.py +445 -0
  35. knowledge/utils/__init__.py +10 -0
  36. knowledge/utils/graph.py +417 -0
  37. knowledge/utils/wikidata.py +197 -0
  38. knowledge/utils/wikipedia.py +175 -0
  39. personal_knowledge_library-3.0.0.dist-info/LICENSE +201 -0
  40. personal_knowledge_library-3.0.0.dist-info/METADATA +1163 -0
  41. personal_knowledge_library-3.0.0.dist-info/RECORD +42 -0
  42. personal_knowledge_library-3.0.0.dist-info/WHEEL +4 -0
@@ -0,0 +1,1163 @@
1
+ Metadata-Version: 2.3
2
+ Name: personal_knowledge_library
3
+ Version: 3.0.0
4
+ Summary: Library to access Wacom's Personal Knowledge graph.
5
+ License: Apache-2.0
6
+ Keywords: semantic-knowledge,knowledge-graph
7
+ Author: Markus Weber
8
+ Author-email: markus.weber@wacom.com
9
+ Requires-Python: >=3.9,<4.0
10
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
11
+ Provides-Extra: dev
12
+ Requires-Dist: Faker (==18.9.0) ; extra == "dev"
13
+ Requires-Dist: OntoSpy (==2.1.1) ; extra == "dev"
14
+ Requires-Dist: PyJWT (==2.10.1)
15
+ Requires-Dist: aiohttp[speedups]
16
+ Requires-Dist: black (>=24.2.0) ; extra == "dev"
17
+ Requires-Dist: cachetools (>=5.3.0)
18
+ Requires-Dist: certifi
19
+ Requires-Dist: flake8 (>=6.0.0) ; extra == "dev"
20
+ Requires-Dist: loguru (==0.7.3)
21
+ Requires-Dist: mypy (>=1.8.0) ; extra == "dev"
22
+ Requires-Dist: ndjson (>=0.3.1)
23
+ Requires-Dist: orjson (>=3.10.0)
24
+ Requires-Dist: pdoc3 ; extra == "dev"
25
+ Requires-Dist: pylint (>=2.17.0) ; extra == "dev"
26
+ Requires-Dist: pytest ; extra == "dev"
27
+ Requires-Dist: pytest-asyncio ; extra == "dev"
28
+ Requires-Dist: pytest-cov ; extra == "dev"
29
+ Requires-Dist: pytest-env ; extra == "dev"
30
+ Requires-Dist: pytest-mock ; extra == "dev"
31
+ Requires-Dist: python-dateutil (>=2.8.2)
32
+ Requires-Dist: rdflib (>=7.1.0)
33
+ Requires-Dist: requests (>=2.32.0)
34
+ Requires-Dist: tox (>=4.0.0) ; extra == "dev"
35
+ Requires-Dist: tqdm (>=4.65.0)
36
+ Description-Content-Type: text/markdown
37
+
38
+ # Wacom Private Knowledge Library
39
+
40
+ [![Python package](https://github.com/Wacom-Developer/personal-knowledge-library/actions/workflows/python-package.yml/badge.svg)](https://github.com/Wacom-Developer/personal-knowledge-library/actions/workflows/python-package.yml)
41
+ [![Pylint](https://github.com/Wacom-Developer/personal-knowledge-library/actions/workflows/pylint.yml/badge.svg)](https://github.com/Wacom-Developer/personal-knowledge-library/actions/workflows/pylint.yml)
42
+
43
+ ![License: Apache 2](https://img.shields.io/badge/License-Apache2-green.svg)
44
+ [![PyPI](https://img.shields.io/pypi/v/personal-knowledge-library.svg)](https://pypi.python.org/pypi/personal-knowledge-library)
45
+ [![PyPI](https://img.shields.io/pypi/pyversions/personal-knowledge-library.svg)](https://pypi.python.org/pypi/personal-knowledge-library)
46
+ [![Documentation](https://img.shields.io/badge/api-reference-blue.svg)](https://developer-docs.wacom.com/docs/private-knowledge-service)
47
+
48
+ ![Contributors](https://img.shields.io/github/contributors/Wacom-Developer/personal-knowledge-library.svg)
49
+ ![GitHub forks](https://img.shields.io/github/forks/Wacom-Developer/personal-knowledge-library.svg)
50
+ ![GitHub stars](https://img.shields.io/github/stars/Wacom-Developer/personal-knowledge-library.svg)
51
+
52
+ The required tenant API key is only available for selected partner companies.
53
+ Please contact your Wacom representative for more information.
54
+
55
+ ## Introduction
56
+
57
+ In knowledge management there is a distinction between data, information and knowledge.
58
+ In the domain of digital ink this means:
59
+
60
+ - **Data** - The equivalent would be the ink strokes
61
+ - **Information** - After using handwriting-, shape-, math-, or other recognition processes ink strokes are converted into machine readable content, such as text, shapes, math representations, other other digital content
62
+ - **Knowledge / Semantics** - Beyond recognition content needs to be semantically analysed to become semantically understood based on a shared common knowledge.
63
+
64
+ The following illustration shows the different layers of knowledge:
65
+ ![Levels of ink knowledge layers](https://github.com/Wacom-Developer/personal-knowledge-library/blob/main/assets/knowledge-levels.png)
66
+
67
+ For handling semantics, Wacom introduced the Wacom Private Knowledge (WPK) cloud service to manage personal ontologies and its associated personal knowledge graph.
68
+
69
+ This library provide simplified access to Wacom's personal knowledge cloud service.
70
+ It contains:
71
+
72
+ - Basic datastructures for Ontology object and entities from the knowledge graph
73
+ - Clients for the REST APIs
74
+ - Connector for Wikidata public knowledge graph
75
+
76
+ **Ontology service:**
77
+
78
+ - List all Ontology structures
79
+ - Modify Ontology structures
80
+ - Delete Ontology structures
81
+
82
+ **Entity service:**
83
+
84
+ - List all entities
85
+ - Add entities to knowledge graph
86
+ - Access object properties
87
+
88
+ **Search service:**
89
+
90
+ - Search for entities for labels and descriptions with a given language
91
+ - Search for literals (data properties)
92
+ - Search for relations (object properties)
93
+
94
+ **Group service:**
95
+
96
+ - List all groups
97
+ - Add groups, modify groups, delete groups
98
+ - Add users and entities to groups
99
+
100
+ **Ontology service:**
101
+
102
+ - List all Ontology structures
103
+ - Modify Ontology structures
104
+
105
+ **Named Entity Linking service:**
106
+
107
+ - Linking words to knowledge entities from graph in a given text (Ontology-based Named Entity Linking)
108
+
109
+ **Wikidata connector:**
110
+
111
+ - Import entities from Wikidata
112
+ - Mapping Wikidata entities to WPK entities
113
+
114
+ # Technology stack
115
+
116
+ ## Domain Knowledge
117
+
118
+ The tasks of the ontology within Wacom's private knowledge system is to formalised the domain the technology is used in, such as education-, smart home-, or creative domein.
119
+ The domain model will be the foundation for the entities collected within the knowledge graph, describing real world concepts in a formal language understood by artificial intelligence system:
120
+
121
+ - Foundation for structured data, knowledge representation as concepts and relations among concepts
122
+ - Being explicit definitions of shared vocabularies for interoperability
123
+ - Being actionable fragments of explicit knowledge that engines can use for inferencing (Reasoning)
124
+ - Can be used for problem solving
125
+
126
+ An ontology defines (specifies) the concepts, relationships, and other distinctions that are relevant for modelling a domain.
127
+
128
+ ## Knowledge Graph
129
+
130
+ - Knowledge graph is generated from unstructured and structured knowledge sources
131
+ - Contains all structured knowledge gathered from all sources
132
+ - Foundation for all semantic algorithms
133
+
134
+ ## Semantic Technology
135
+
136
+ - Extract knowledge from various sources (Connectors)
137
+ - Linking words to knowledge entities from graph in a given text (Ontology-based Named Entity Linking)
138
+ - Enables a smart search functionality which understands the context and finds related documents (Semantic Search)
139
+
140
+
141
+ # Functionality
142
+
143
+ ## Import Format
144
+
145
+ For importing entities into the knowledge graph, the tools/import_entities.py script can be used.
146
+
147
+ The ThingObject support a NDJSON based import format, where the individual JSON files can contain the following structure.
148
+
149
+ | Field name | Subfield name | Data Structure | Description |
150
+ |------------------------|---------------|----------------|------------------------------------------------------------------------------------------------|
151
+ | source_reference_id | | str | A unique identifier for the entity used in the source system |
152
+ | source_system | | str | The source system describes the original source of the entity, such as wikidata, youtube, ... |
153
+ | image | | str | A string representing the URL of the entity's icon. |
154
+ | labels | | array | An array of label objects, where each object has the following fields: |
155
+ | | value | str | A string representing the label text in the specified locale. |
156
+ | | locale | str | A string combining the ISO-3166 country code and the ISO-639 language code (e.g., "en-US"). |
157
+ | | isMain | bool | A boolean flag indicating if this label is the main label for the entity (true) or an alias (false). |
158
+ | descriptions | | array | An array of description objects, where each object has the following fields: |
159
+ | | description | str | A string representing the description text in the specified locale. |
160
+ | | locale | str | A string combining the ISO-3166 country code and the ISO-639 language code (e.g., "en-US"). |
161
+ | type | | str | A string representing the IRI of the ontology class for this entity. |
162
+ | literals | | array[map] | An array of data property objects, where each object has the following fields: |
163
+
164
+
165
+ ## Access API
166
+
167
+ The personal knowledge graph backend is implement as a multi-tenancy system.
168
+ Thus, several tenants can be logically separated from each other and different organisations can build their one knowledge graph.
169
+
170
+ ![Tenant concept](https://github.com/Wacom-Developer/personal-knowledge-library/blob/main/assets/tenant-concept.png)
171
+
172
+ In general, a tenant with their users, groups, and entities are logically separated.
173
+ Physically the entities are store in the same instance of the Wacom Private Knowledge (WPK) backend database system.
174
+
175
+ The user management is rather limited, each organisation must provide their own authentication service and user management.
176
+ The backend only has a reference of the user (*“shadow user”*) by an **external user id**.
177
+
178
+ The management of tenants is limited to the system owner - Wacom -, as it requires a **tenant management API** key.
179
+ While users for each tenant can be created by the owner of the **Tenant API Key**.
180
+ You will receive this token from the system owner after the creation of the tenant.
181
+
182
+
183
+ > :warning: Store the **Tenant API Key** in a secure key store, as attackers can use the key to harm your system.
184
+
185
+
186
+ The **Tenant API Key** should be only used by your authentication service to create shadow users and to login your user into the WPK backend.
187
+ After a successful user login, you will receive a token which can be used by the user to create, update, or delete entities and relations.
188
+
189
+ The following illustration summarizes the flows for creation of tenant and users:
190
+
191
+ ![Tenant and user creation](https://github.com/Wacom-Developer/personal-knowledge-library/blob/main/assets/tenant-user-creation.png)
192
+
193
+ The organisation itself needs to implement their own authentication service which:
194
+
195
+ - handles the users and their passwords,
196
+ - controls the personal data of the users,
197
+ - connects the users with the WPK backend and share with them the user token.
198
+
199
+ The WPK backend only manages the access levels of the entities and the group management for users.
200
+ The illustration shows how the access token is received from the WPK endpoint:
201
+
202
+ ![Access token request.](https://github.com/Wacom-Developer/personal-knowledge-library/blob/main/assets/access-token.png)
203
+
204
+ # Entity API
205
+
206
+ The entities used within the knowledge graph and the relationship among them is defined within an ontology that is manage with Wacom Ontology Management System (WOMS).
207
+
208
+ An entity within the personal knowledge graphs consist of these major parts:
209
+
210
+ - **Icon** - a visual representation of the entity, for instance a portrait of a person.
211
+ - **URI** - a unique resource identifier of an entity in the graph.
212
+ - **Type** - the type links to the defined concept class in the ontology.
213
+ - **Labels** - labels are the word(s) use in a language for the concept.
214
+ - **Description** - a short abstract that describes the entity.
215
+ - **Literals** - literals are properties of an entity, such as first name of a person. The ontology defines all literals of the concept class as well as its data type.
216
+ - **Relations** - the relationship among different entities is described using relations.
217
+
218
+ The following illustration provides an example for an entity:
219
+
220
+ ![Entity description](https://github.com/Wacom-Developer/personal-knowledge-library/blob/main/assets/entity-description.png)
221
+
222
+ ## Entity content
223
+
224
+ Entities in general are language-independent as across nationalities or cultures we only use different scripts and words for a shared instance of a concept.
225
+
226
+ Let's take Leonardo da Vinci as an example.
227
+ The ontology defines the concept of a Person, a human being.
228
+ Now, in English its label would be _Leonardo da Vinci_, while in Japanese _レオナルド・ダ・ヴィンチ_.
229
+ Moreover, he is also known as _Leonardo di ser Piero da Vinci_ or _ダ・ビンチ_.
230
+
231
+ ### Labels
232
+
233
+ Now, in the given example all words that a assigned to the concept are labels.
234
+ The label _Leonardo da Vinci_ is stored in the backend with an additional language code, e.g. _en_.
235
+
236
+ There is always a main label, which refers to the most common or official name of entity.
237
+ Another example would be Wacom, where _Wacom Co., Ltd._ is the official name while _Wacom_ is commonly used and be considered as an alias.
238
+
239
+ > :pushpin: For the language code the **ISO 639-1:2002**, codes for the representation of names of languages—Part 1: Alpha-2 code. Read more, [here](https://www.iso.org/standard/22109.html)
240
+
241
+ ## Samples
242
+
243
+ ### Entity handling
244
+
245
+ This samples shows how to work with graph service.
246
+
247
+ ```python
248
+ import argparse
249
+ from typing import Optional, Dict, List
250
+
251
+ from knowledge.base.entity import Description, Label
252
+ from knowledge.base.language import LocaleCode, EN_US, DE_DE
253
+ from knowledge.base.ontology import OntologyClassReference, OntologyPropertyReference, ThingObject, ObjectProperty
254
+ from knowledge.services.graph import WacomKnowledgeService
255
+
256
+ # ------------------------------- Knowledge entities -------------------------------------------------------------------
257
+ LEONARDO_DA_VINCI: str = 'Leonardo da Vinci'
258
+ SELF_PORTRAIT_STYLE: str = 'self-portrait'
259
+ ICON: str = "https://upload.wikimedia.org/wikipedia/commons/thumb/8/87/Mona_Lisa_%28copy%2C_Thalwil%2C_Switzerland%29."\
260
+ "JPG/1024px-Mona_Lisa_%28copy%2C_Thalwil%2C_Switzerland%29.JPG"
261
+ # ------------------------------- Ontology class names -----------------------------------------------------------------
262
+ THING_OBJECT: OntologyClassReference = OntologyClassReference('wacom', 'core', 'Thing')
263
+ """
264
+ The Ontology will contain a Thing class where is the root class in the hierarchy.
265
+ """
266
+ ARTWORK_CLASS: OntologyClassReference = OntologyClassReference('wacom', 'creative', 'VisualArtwork')
267
+ PERSON_CLASS: OntologyClassReference = OntologyClassReference('wacom', 'core', 'Person')
268
+ ART_STYLE_CLASS: OntologyClassReference = OntologyClassReference.parse('wacom:creative#ArtStyle')
269
+ IS_CREATOR: OntologyPropertyReference = OntologyPropertyReference('wacom', 'core', 'created')
270
+ HAS_TOPIC: OntologyPropertyReference = OntologyPropertyReference.parse('wacom:core#hasTopic')
271
+ CREATED: OntologyPropertyReference = OntologyPropertyReference.parse('wacom:core#created')
272
+ HAS_ART_STYLE: OntologyPropertyReference = OntologyPropertyReference.parse('wacom:creative#hasArtstyle')
273
+
274
+
275
+ def print_entity(display_entity: ThingObject, list_idx: int, client: WacomKnowledgeService,
276
+ short: bool = False):
277
+ """
278
+ Printing entity details.
279
+
280
+ Parameters
281
+ ----------
282
+ display_entity: ThingObject
283
+ Entity with properties
284
+ list_idx: int
285
+ Index with a list
286
+ client: WacomKnowledgeService
287
+ Knowledge graph client
288
+ short: bool
289
+ Short summary
290
+ """
291
+ print(f'[{list_idx}] : {display_entity.uri} <{display_entity.concept_type.iri}>')
292
+ if len(display_entity.label) > 0:
293
+ print(' | [Labels]')
294
+ for la in display_entity.label:
295
+ print(f' | |- "{la.content}"@{la.language_code}')
296
+ print(' |')
297
+ if not short:
298
+ if len(display_entity.alias) > 0:
299
+ print(' | [Alias]')
300
+ for la in display_entity.alias:
301
+ print(f' | |- "{la.content}"@{la.language_code}')
302
+ print(' |')
303
+ if len(display_entity.data_properties) > 0:
304
+ print(' | [Attributes]')
305
+ for data_property, labels in display_entity.data_properties.items():
306
+ print(f' | |- {data_property.iri}:')
307
+ for li in labels:
308
+ print(f' | |-- "{li.value}"@{li.language_code}')
309
+ print(' |')
310
+
311
+ relations_obj: Dict[OntologyPropertyReference, ObjectProperty] = client.relations(uri=display_entity.uri)
312
+ if len(relations_obj) > 0:
313
+ print(' | [Relations]')
314
+ for r_idx, re in enumerate(relations_obj.values()):
315
+ last: bool = r_idx == len(relations_obj) - 1
316
+ print(f' |--- {re.relation.iri}: ')
317
+ print(f' {"|" if not last else " "} |- [Incoming]: {re.incoming_relations} ')
318
+ print(f' {"|" if not last else " "} |- [Outgoing]: {re.outgoing_relations}')
319
+ print()
320
+
321
+
322
+ if __name__ == '__main__':
323
+ parser = argparse.ArgumentParser()
324
+ parser.add_argument("-u", "--user", help="External Id of the shadow user within the Wacom Personal Knowledge.",
325
+ required=True)
326
+ parser.add_argument("-t", "--tenant", help="Tenant Id of the shadow user within the Wacom Personal Knowledge.",
327
+ required=True)
328
+ parser.add_argument("-i", "--instance", default='https://private-knowledge.wacom.com',
329
+ help="URL of instance")
330
+ args = parser.parse_args()
331
+ TENANT_KEY: str = args.tenant
332
+ EXTERNAL_USER_ID: str = args.user
333
+ # Wacom personal knowledge REST API Client
334
+ knowledge_client: WacomKnowledgeService = WacomKnowledgeService(application_name="Wacom Knowledge Listing",
335
+ service_url=args.instance)
336
+ knowledge_client.login(args.tenant, args.user)
337
+ page_id: Optional[str] = None
338
+ page_number: int = 1
339
+ entity_count: int = 0
340
+ print('-----------------------------------------------------------------------------------------------------------')
341
+ print(' First step: Find Leonardo da Vinci in the knowledge graph.')
342
+ print('-----------------------------------------------------------------------------------------------------------')
343
+ res_entities, next_search_page = knowledge_client.search_labels(search_term=LEONARDO_DA_VINCI,
344
+ language_code=LocaleCode('en_US'), limit=1000)
345
+ leo: Optional[ThingObject] = None
346
+ s_idx: int = 1
347
+ for res_entity in res_entities:
348
+ # Entity must be a person and the label match with full string
349
+ if res_entity.concept_type == PERSON_CLASS and LEONARDO_DA_VINCI in [la.content for la in res_entity.label]:
350
+ leo = res_entity
351
+ break
352
+
353
+ print('-----------------------------------------------------------------------------------------------------------')
354
+ print(' What artwork exists in the knowledge graph.')
355
+ print('-----------------------------------------------------------------------------------------------------------')
356
+ relations_dict: Dict[OntologyPropertyReference, ObjectProperty] = knowledge_client.relations(uri=leo.uri)
357
+ print(f' Artwork of {leo.label}')
358
+ print('-----------------------------------------------------------------------------------------------------------')
359
+ idx: int = 1
360
+ if CREATED in relations_dict:
361
+ for e in relations_dict[CREATED].outgoing_relations:
362
+ print(f' [{idx}] {e.uri}: {e.label}')
363
+ idx += 1
364
+ print('-----------------------------------------------------------------------------------------------------------')
365
+ print(' Let us create a new piece of artwork.')
366
+ print('-----------------------------------------------------------------------------------------------------------')
367
+
368
+ # Main labels for entity
369
+ artwork_labels: List[Label] = [
370
+ Label('Ginevra Gherardini', EN_US),
371
+ Label('Ginevra Gherardini', DE_DE)
372
+ ]
373
+ # Alias labels for entity
374
+ artwork_alias: List[Label] = [
375
+ Label("Ginevra", EN_US),
376
+ Label("Ginevra", DE_DE)
377
+ ]
378
+ # Topic description
379
+ artwork_description: List[Description] = [
380
+ Description('Oil painting of Mona Lisa\' sister', EN_US),
381
+ Description('Ölgemälde von Mona Lisa\' Schwester', DE_DE)
382
+ ]
383
+ # Topic
384
+ artwork_object: ThingObject = ThingObject(label=artwork_labels, concept_type=ARTWORK_CLASS,
385
+ description=artwork_description,
386
+ icon=ICON)
387
+ artwork_object.alias = artwork_alias
388
+ print(f' Create: {artwork_object}')
389
+ # Create artwork
390
+ artwork_entity_uri: str = knowledge_client.create_entity(artwork_object)
391
+ print(f' Entity URI: {artwork_entity_uri}')
392
+ # Create relation between Leonardo da Vinci and artwork
393
+ knowledge_client.create_relation(source=leo.uri, relation=IS_CREATOR, target=artwork_entity_uri)
394
+
395
+ relations_dict = knowledge_client.relations(uri=artwork_entity_uri)
396
+ for ontology_property, object_property in relations_dict.items():
397
+ print(f' {object_property}')
398
+ # You will see that wacom:core#isCreatedBy is automatically inferred as relation as it is the inverse property of
399
+ # wacom:core#created.
400
+
401
+ # Now, more search options
402
+ res_entities, next_search_page = knowledge_client.search_description('Michelangelo\'s Sistine Chapel',
403
+ EN_US, limit=1000)
404
+ print('-----------------------------------------------------------------------------------------------------------')
405
+ print(' Search results. Description: "Michelangelo\'s Sistine Chapel"')
406
+ print('-----------------------------------------------------------------------------------------------------------')
407
+ s_idx: int = 1
408
+ for e in res_entities:
409
+ print_entity(e, s_idx, knowledge_client)
410
+
411
+ # Now, let's search all artwork that has the art style self-portrait
412
+ res_entities, next_search_page = knowledge_client.search_labels(search_term=SELF_PORTRAIT_STYLE,
413
+ language_code=EN_US, limit=1000)
414
+ art_style: Optional[ThingObject] = None
415
+ s_idx: int = 1
416
+ for entity in res_entities:
417
+ # Entity must be a person and the label match with full string
418
+ if entity.concept_type == ART_STYLE_CLASS and SELF_PORTRAIT_STYLE in [la.content for la in entity.label]:
419
+ art_style = entity
420
+ break
421
+ res_entities, next_search_page = knowledge_client.search_relation(subject_uri=None,
422
+ relation=HAS_ART_STYLE,
423
+ object_uri=art_style.uri,
424
+ language_code=EN_US)
425
+ print('-----------------------------------------------------------------------------------------------------------')
426
+ print(' Search results. Relation: relation:=has_topic object_uri:= unknown')
427
+ print('-----------------------------------------------------------------------------------------------------------')
428
+ s_idx: int = 1
429
+ for e in res_entities:
430
+ print_entity(e, s_idx, knowledge_client, short=True)
431
+ s_idx += 1
432
+
433
+ # Finally, the activation function retrieving the related identities to a pre-defined depth.
434
+ entities, relations = knowledge_client.activations(uris=[leo.uri], depth=1)
435
+ print('-----------------------------------------------------------------------------------------------------------')
436
+ print(f'Activation. URI: {leo.uri}')
437
+ print('-----------------------------------------------------------------------------------------------------------')
438
+ s_idx: int = 1
439
+ for e in res_entities:
440
+ print_entity(e, s_idx, knowledge_client)
441
+ s_idx += 1
442
+ # All relations
443
+ print('-----------------------------------------------------------------------------------------------------------')
444
+ for r in relations:
445
+ print(f'Subject: {r[0]} Predicate: {r[1]} Object: {r[2]}')
446
+ print('-----------------------------------------------------------------------------------------------------------')
447
+ page_id = None
448
+
449
+ # Listing all entities which have the type
450
+ idx: int = 1
451
+ while True:
452
+ # pull
453
+ entities, total_number, next_page_id = knowledge_client.listing(ART_STYLE_CLASS, page_id=page_id, limit=100)
454
+ pulled_entities: int = len(entities)
455
+ entity_count += pulled_entities
456
+ print('-------------------------------------------------------------------------------------------------------')
457
+ print(f' Page: {page_number} Number of entities: {len(entities)} ({entity_count}/{total_number}) '
458
+ f'Next page id: {next_page_id}')
459
+ print('-------------------------------------------------------------------------------------------------------')
460
+ for e in entities:
461
+ print_entity(e, idx, knowledge_client)
462
+ idx += 1
463
+ if pulled_entities == 0:
464
+ break
465
+ page_number += 1
466
+ page_id = next_page_id
467
+ print()
468
+ # Delete all personal entities for this user
469
+ while True:
470
+ # pull
471
+ entities, total_number, next_page_id = knowledge_client.listing(THING_OBJECT, page_id=page_id,
472
+ limit=100)
473
+ pulled_entities: int = len(entities)
474
+ if pulled_entities == 0:
475
+ break
476
+ delete_uris: List[str] = [e.uri for e in entities]
477
+ print(f'Cleanup. Delete entities: {delete_uris}')
478
+ knowledge_client.delete_entities(uris=delete_uris, force=True)
479
+ page_number += 1
480
+ page_id = next_page_id
481
+ print('-----------------------------------------------------------------------------------------------------------')
482
+ ```
483
+
484
+ ### Named Entity Linking
485
+
486
+ Performing Named Entity Linking (NEL) on text and Universal Ink Model.
487
+
488
+ ```python
489
+ import argparse
490
+ from typing import List, Dict
491
+
492
+ import urllib3
493
+
494
+ from knowledge.base.language import EN_US
495
+ from knowledge.base.ontology import OntologyPropertyReference, ThingObject, ObjectProperty
496
+ from knowledge.nel.base import KnowledgeGraphEntity
497
+ from knowledge.nel.engine import WacomEntityLinkingEngine
498
+ from knowledge.services.graph import WacomKnowledgeService
499
+
500
+ urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
501
+
502
+
503
+ TEXT: str = "Leonardo da Vinci painted the Mona Lisa."
504
+
505
+
506
+ def print_entity(entity: KnowledgeGraphEntity, list_idx: int, auth_key: str, client: WacomKnowledgeService):
507
+ """
508
+ Printing entity details.
509
+
510
+ Parameters
511
+ ----------
512
+ entity: KnowledgeGraphEntity
513
+ Named entity
514
+ list_idx: int
515
+ Index with a list
516
+ auth_key: str
517
+ Authorization key
518
+ client: WacomKnowledgeService
519
+ Knowledge graph client
520
+ """
521
+ thing: ThingObject = knowledge_client.entity(auth_key=user_token, uri=entity.entity_source.uri)
522
+ print(f'[{list_idx}] - {entity.ref_text} [{entity.start_idx}-{entity.end_idx}] : {thing.uri}'
523
+ f' <{thing.concept_type.iri}>')
524
+ if len(thing.label) > 0:
525
+ print(' | [Labels]')
526
+ for la in thing.label:
527
+ print(f' | |- "{la.content}"@{la.language_code}')
528
+ print(' |')
529
+ if len(thing.label) > 0:
530
+ print(' | [Alias]')
531
+ for la in thing.alias:
532
+ print(f' | |- "{la.content}"@{la.language_code}')
533
+ print(' |')
534
+ relations: Dict[OntologyPropertyReference, ObjectProperty] = client.relations(auth_key=auth_key, uri=thing.uri)
535
+ if len(thing.data_properties) > 0:
536
+ print(' | [Attributes]')
537
+ for data_property, labels in thing.data_properties.items():
538
+ print(f' | |- {data_property.iri}:')
539
+ for li in labels:
540
+ print(f' | |-- "{li.value}"@{li.language_code}')
541
+ print(' |')
542
+ if len(relations) > 0:
543
+ print(' | [Relations]')
544
+ for re in relations.values():
545
+ print(f' |--- {re.relation.iri}: ')
546
+ print(f' |- [Incoming]: {re.incoming_relations} ')
547
+ print(f' |- [Outgoing]: {re.outgoing_relations}')
548
+ print()
549
+
550
+
551
+ if __name__ == '__main__':
552
+ parser = argparse.ArgumentParser()
553
+ parser.add_argument("-u", "--user", help="External Id of the shadow user within the Wacom Personal Knowledge.",
554
+ required=True)
555
+ parser.add_argument("-t", "--tenant", help="Tenant Id of the shadow user within the Wacom Personal Knowledge.",
556
+ required=True)
557
+ parser.add_argument("-i", "--instance", default="https://private-knowledge.wacom.com", help="URL of instance")
558
+ args = parser.parse_args()
559
+ TENANT_KEY: str = args.tenant
560
+ EXTERNAL_USER_ID: str = args.user
561
+ # Wacom personal knowledge REST API Client
562
+ knowledge_client: WacomKnowledgeService = WacomKnowledgeService(
563
+ application_name="Named Entity Linking Knowledge access",
564
+ service_url=args.instance)
565
+ # Wacom Named Entity Linking
566
+ nel_client: WacomEntityLinkingEngine = WacomEntityLinkingEngine(
567
+ service_url=args.instance,
568
+ service_endpoint=WacomEntityLinkingEngine.SERVICE_ENDPOINT
569
+ )
570
+ # Use special tenant for testing: Unit-test tenant
571
+ user_token, refresh_token, expiration_time = nel_client.request_user_token(TENANT_KEY, EXTERNAL_USER_ID)
572
+ entities: List[KnowledgeGraphEntity] = nel_client.\
573
+ link_personal_entities(text=TEXT, language_code=EN_US, auth_key=user_token)
574
+ idx: int = 1
575
+ print('-----------------------------------------------------------------------------------------------------------')
576
+ print(f'Text: "{TEXT}"@{EN_US}')
577
+ print('-----------------------------------------------------------------------------------------------------------')
578
+ for e in entities:
579
+ print_entity(e, idx, user_token, knowledge_client)
580
+ idx += 1
581
+
582
+ ```
583
+
584
+ ### Access Management
585
+
586
+ The sample shows, how access to entities can be shared with a group of users or the tenant.
587
+
588
+ ```python
589
+ import argparse
590
+ from typing import List
591
+
592
+ from knowledge.base.entity import Label, Description
593
+ from knowledge.base.language import EN_US, DE_DE, JA_JP
594
+ from knowledge.base.ontology import OntologyClassReference, ThingObject
595
+ from knowledge.services.base import WacomServiceException
596
+ from knowledge.services.graph import WacomKnowledgeService
597
+ from knowledge.services.group import GroupManagementService, Group
598
+ from knowledge.services.users import UserManagementServiceAPI
599
+
600
+ # ------------------------------- User credential ----------------------------------------------------------------------
601
+ TOPIC_CLASS: OntologyClassReference = OntologyClassReference('wacom', 'core', 'Topic')
602
+
603
+
604
+ def create_entity() -> ThingObject:
605
+ """Create a new entity.
606
+
607
+ Returns
608
+ -------
609
+ entity: ThingObject
610
+ Entity object
611
+ """
612
+ # Main labels for entity
613
+ topic_labels: List[Label] = [
614
+ Label('Hidden', EN_US),
615
+ Label('Versteckt', DE_DE),
616
+ Label('隠れた', JA_JP),
617
+ ]
618
+
619
+ # Topic description
620
+ topic_description: List[Description] = [
621
+ Description('Hidden entity to explain access management.', EN_US),
622
+ Description('Verstecke Entität, um die Zugriffsteuerung zu erlären.', DE_DE)
623
+ ]
624
+ # Topic
625
+ topic_object: ThingObject = ThingObject(label=topic_labels, concept_type=TOPIC_CLASS, description=topic_description)
626
+ return topic_object
627
+
628
+
629
+ if __name__ == '__main__':
630
+ parser = argparse.ArgumentParser()
631
+ parser.add_argument("-u", "--user", help="External Id of the shadow user within the Wacom Personal Knowledge.",
632
+ required=True)
633
+ parser.add_argument("-t", "--tenant", help="Tenant Id of the shadow user within the Wacom Personal Knowledge.",
634
+ required=True)
635
+ parser.add_argument("-i", "--instance", default='https://private-knowledge.wacom.com',
636
+ help="URL of instance")
637
+ args = parser.parse_args()
638
+ TENANT_KEY: str = args.tenant
639
+ EXTERNAL_USER_ID: str = args.user
640
+ # Wacom personal knowledge REST API Client
641
+ knowledge_client: WacomKnowledgeService = WacomKnowledgeService(application_name="Wacom Knowledge Listing",
642
+ service_url=args.instance)
643
+ # User Management
644
+ user_management: UserManagementServiceAPI = UserManagementServiceAPI(service_url=args.instance)
645
+ # Group Management
646
+ group_management: GroupManagementService = GroupManagementService(service_url=args.instance)
647
+ admin_token, refresh_token, expiration_time = user_management.request_user_token(TENANT_KEY, EXTERNAL_USER_ID)
648
+ # Now, we create a users
649
+ u1, u1_token, _, _ = user_management.create_user(TENANT_KEY, "u1")
650
+ u2, u2_token, _, _ = user_management.create_user(TENANT_KEY, "u2")
651
+ u3, u3_token, _, _ = user_management.create_user(TENANT_KEY, "u3")
652
+
653
+ # Now, let's create an entity
654
+ thing: ThingObject = create_entity()
655
+ entity_uri: str = knowledge_client.create_entity(thing, auth_key=u1_token)
656
+ # Only user 1 can access the entity from cloud storage
657
+ my_thing: ThingObject = knowledge_client.entity(entity_uri, auth_key=u1_token)
658
+ print(f'User is the owner of {my_thing.owner}')
659
+ # Now only user 1 has access to the personal entity
660
+ knowledge_client.entity(entity_uri, auth_key=u1_token)
661
+ # Try to access the entity
662
+ try:
663
+ knowledge_client.entity(entity_uri, auth_key=u2_token)
664
+ except WacomServiceException as we:
665
+ print(f"Expected exception as user 2 has no access to the personal entity of user 1. Exception: {we}")
666
+ print(f"Status code: {we.status_code}")
667
+ print(f"Response text: {we.service_response}")
668
+ # Try to access the entity
669
+ try:
670
+ knowledge_client.entity(entity_uri, auth_key=u3_token)
671
+ except WacomServiceException as we:
672
+ print(f"Expected exception as user 3 has no access to the personal entity of user 1. Exception: {we}")
673
+ # Now, user 1 creates a group
674
+ g: Group = group_management.create_group("test-group", auth_key=u1_token)
675
+ # Shares the join key with user 2 and user 2 joins
676
+ group_management.join_group(g.id, g.join_key, auth_key=u2_token)
677
+ # Share entity with group
678
+ group_management.add_entity_to_group(g.id, entity_uri, auth_key=u1_token)
679
+ # Now, user 2 should have access
680
+ other_thing: ThingObject = knowledge_client.entity(entity_uri, auth_key=u2_token)
681
+ print(f'User 2 is the owner of the thing: {other_thing.owner}')
682
+ # Try to access the entity
683
+ try:
684
+ knowledge_client.entity(entity_uri, auth_key=u3_token)
685
+ except WacomServiceException as we:
686
+ print(f"Expected exception as user 3 still has no access to the personal entity of user 1. Exception: {we}")
687
+ print(f"URL: {we.url}, method: {we.method}")
688
+ print(f"Status code: {we.status_code}")
689
+ print(f"Response text: {we.service_response}")
690
+ print(f"Message: {we.message}")
691
+ # Un-share the entity
692
+ group_management.remove_entity_to_group(g.id, entity_uri, auth_key=u1_token)
693
+ # Now, again no access
694
+ try:
695
+ knowledge_client.entity(entity_uri, auth_key=u2_token)
696
+ except WacomServiceException as we:
697
+ print(f"Expected exception as user 2 has no access to the personal entity of user 1. Exception: {we}")
698
+ print(f"URL: {we.url}, method: {we.method}")
699
+ print(f"Status code: {we.status_code}")
700
+ print(f"Response text: {we.service_response}")
701
+ print(f"Message: {we.message}")
702
+ group_management.leave_group(group_id=g.id, auth_key=u2_token)
703
+ # Now, share the entity with the whole tenant
704
+ my_thing.tenant_access_right.read = True
705
+ knowledge_client.update_entity(my_thing, auth_key=u1_token)
706
+ # Now, all users can access the entity
707
+ knowledge_client.entity(entity_uri, auth_key=u2_token)
708
+ knowledge_client.entity(entity_uri, auth_key=u3_token)
709
+ # Finally, clean up
710
+ knowledge_client.delete_entity(entity_uri, force=True, auth_key=u1_token)
711
+ # Remove users
712
+ user_management.delete_user(TENANT_KEY, u1.external_user_id, u1.id, force=True)
713
+ user_management.delete_user(TENANT_KEY, u2.external_user_id, u2.id, force=True)
714
+ user_management.delete_user(TENANT_KEY, u3.external_user_id, u3.id, force=True)
715
+
716
+ ```
717
+
718
+ ### Ontology Creation
719
+
720
+ The samples show how the ontology can be extended and new entities can be added using the added classes.
721
+
722
+ ```python
723
+ import argparse
724
+ import sys
725
+ from typing import Optional, List
726
+
727
+ from knowledge.base.entity import Label, Description
728
+ from knowledge.base.language import EN_US, DE_DE
729
+ from knowledge.base.ontology import DataPropertyType, OntologyClassReference, OntologyPropertyReference, ThingObject, \
730
+ DataProperty, OntologyContext
731
+ from knowledge.services.graph import WacomKnowledgeService
732
+ from knowledge.services.ontology import OntologyService
733
+ from knowledge.services.session import PermanentSession
734
+
735
+ # ------------------------------- Constants ----------------------------------------------------------------------------
736
+ LEONARDO_DA_VINCI: str = 'Leonardo da Vinci'
737
+ CONTEXT_NAME: str = 'core'
738
+ # Wacom Base Ontology Types
739
+ PERSON_TYPE: OntologyClassReference = OntologyClassReference.parse("wacom:core#Person")
740
+ # Demo Class
741
+ ARTIST_TYPE: OntologyClassReference = OntologyClassReference.parse("demo:creative#Artist")
742
+ # Demo Object property
743
+ IS_INSPIRED_BY: OntologyPropertyReference = OntologyPropertyReference.parse("demo:creative#isInspiredBy")
744
+ # Demo Data property
745
+ STAGE_NAME: OntologyPropertyReference = OntologyPropertyReference.parse("demo:creative#stageName")
746
+
747
+
748
+ def create_artist() -> ThingObject:
749
+ """
750
+ Create a new artist entity.
751
+ Returns
752
+ -------
753
+ instance: ThingObject
754
+ Artist entity
755
+ """
756
+ # Main labels for entity
757
+ topic_labels: List[Label] = [
758
+ Label('Gian Giacomo Caprotti', EN_US),
759
+ ]
760
+
761
+ # Topic description
762
+ topic_description: List[Description] = [
763
+ Description('Hidden entity to explain access management.', EN_US),
764
+ Description('Verstecke Entität, um die Zugriffsteuerung zu erlären.', DE_DE)
765
+ ]
766
+
767
+ data_property: DataProperty = DataProperty(content='Salaj',
768
+ property_ref=STAGE_NAME,
769
+ language_code=EN_US)
770
+ # Topic
771
+ artist: ThingObject = ThingObject(label=topic_labels, concept_type=ARTIST_TYPE, description=topic_description)
772
+ artist.add_data_property(data_property)
773
+ return artist
774
+
775
+
776
+ if __name__ == '__main__':
777
+ parser = argparse.ArgumentParser()
778
+ parser.add_argument("-u", "--user", help="External Id of the shadow user within the Wacom Personal Knowledge.",
779
+ required=True)
780
+ parser.add_argument("-t", "--tenant", help="Tenant Id of the shadow user within the Wacom Personal Knowledge.",
781
+ required=True)
782
+ parser.add_argument("-i", "--instance", default="https://private-knowledge.wacom.com", help="URL of instance")
783
+ args = parser.parse_args()
784
+ TENANT_KEY: str = args.tenant
785
+ EXTERNAL_USER_ID: str = args.user
786
+ # Wacom Ontology REST API Client
787
+ ontology_client: OntologyService = OntologyService(service_url=args.instance)
788
+ knowledge_client: WacomKnowledgeService = WacomKnowledgeService(
789
+ application_name="Ontology Creation Demo",
790
+ service_url=args.instance)
791
+ # Login as admin user
792
+ session: PermanentSession = ontology_client.login(TENANT_KEY, EXTERNAL_USER_ID)
793
+ if session.roles != "TenantAdmin":
794
+ print(f'User {EXTERNAL_USER_ID} is not an admin user.')
795
+ sys.exit(1)
796
+ knowledge_client.use_session(session.id)
797
+ knowledge_client.ontology_update()
798
+ context: Optional[OntologyContext] = ontology_client.context()
799
+ if context is None:
800
+ # First, create a context for the ontology
801
+ ontology_client.create_context(name=CONTEXT_NAME, base_uri=f'demo:{CONTEXT_NAME}')
802
+ context_name: str = CONTEXT_NAME
803
+ else:
804
+ context_name: str = context.context
805
+ # Creating a class which is a subclass of a person
806
+ ontology_client.create_concept(context_name, reference=ARTIST_TYPE, subclass_of=PERSON_TYPE)
807
+
808
+ # Object properties
809
+ ontology_client.create_object_property(context=context_name, reference=IS_INSPIRED_BY, domains_cls=[ARTIST_TYPE],
810
+ ranges_cls=[PERSON_TYPE], inverse_of=None, subproperty_of=None)
811
+ # Data properties
812
+ ontology_client.create_data_property(context=context_name, reference=STAGE_NAME,
813
+ domains_cls=[ARTIST_TYPE],
814
+ ranges_cls=[DataPropertyType.STRING],
815
+ subproperty_of=None)
816
+ # Commit the changes of the ontology. This is very important to confirm changes.
817
+ ontology_client.commit(context=context_name)
818
+ # Trigger graph service. After the update the ontology is available and the new entities can be created
819
+ knowledge_client.ontology_update()
820
+
821
+ res_entities, next_search_page = knowledge_client.search_labels(search_term=LEONARDO_DA_VINCI,
822
+ language_code=EN_US, limit=1000)
823
+ leo: Optional[ThingObject] = None
824
+ for entity in res_entities:
825
+ # Entity must be a person and the label match with full string
826
+ if entity.concept_type == PERSON_TYPE and LEONARDO_DA_VINCI in [la.content for la in entity.label]:
827
+ leo = entity
828
+ break
829
+
830
+ artist_student: ThingObject = create_artist()
831
+ artist_student_uri: str = knowledge_client.create_entity(artist_student)
832
+ knowledge_client.create_relation(artist_student_uri, IS_INSPIRED_BY, leo.uri)
833
+
834
+ ```
835
+
836
+ ### Asynchronous Client
837
+
838
+ The sample shows how to use the asynchronous client.
839
+ Most of the methods are available in the asynchronous client(s).
840
+ Only for the ontology management the asynchronous client is not available.
841
+
842
+ ```python
843
+ import argparse
844
+ import asyncio
845
+ import uuid
846
+ from pathlib import Path
847
+ from typing import Tuple, List, Dict, Any, Optional
848
+
849
+ from knowledge.base.entity import Label
850
+ from knowledge.base.language import LanguageCode, EN, SUPPORTED_LOCALES, EN_US
851
+ from knowledge.base.ontology import ThingObject
852
+ from knowledge.ontomapping import load_configuration
853
+ from knowledge.ontomapping.manager import wikidata_to_thing
854
+ from knowledge.public.relations import wikidata_relations_extractor
855
+ from knowledge.public.wikidata import WikidataSearchResult, WikiDataAPIClient, WikidataThing
856
+ from knowledge.services.asyncio.graph import AsyncWacomKnowledgeService
857
+ from knowledge.services.asyncio.group import AsyncGroupManagementService
858
+ from knowledge.services.asyncio.users import AsyncUserManagementService
859
+ from knowledge.services.base import WacomServiceException, format_exception
860
+ from knowledge.services.group import Group
861
+ from knowledge.services.session import PermanentSession, RefreshableSession
862
+ from knowledge.services.users import UserRole, User
863
+
864
+
865
+ def import_entity_from_wikidata(search_term: str, locale: LanguageCode) -> Dict[str, ThingObject]:
866
+ """
867
+ Import entity from Wikidata.
868
+ Parameters
869
+ ----------
870
+ search_term: str
871
+ Search term
872
+ locale: LanguageCode
873
+ Language code
874
+
875
+ Returns
876
+ -------
877
+ things: Dict[str, ThingObject]
878
+ Mapping qid to thing object
879
+ """
880
+ search_results: List[WikidataSearchResult] = WikiDataAPIClient.search_term(search_term, locale)
881
+ # Load mapping configuration
882
+ load_configuration(Path(__file__).parent.parent / 'pkl-cache' / 'ontology_mapping.json')
883
+ # Search wikidata for entities
884
+ qid_entities: List[WikidataThing] = WikiDataAPIClient.retrieve_entities([sr.qid for sr in search_results])
885
+ qid_things: Dict[str, WikidataThing] = {qt.qid: qt for qt in qid_entities}
886
+ relations: Dict[str, List[Dict[str, Any]]] = wikidata_relations_extractor(qid_things)
887
+ # Now, let's create the things
888
+ things: Dict[str, ThingObject] = {}
889
+ for res in qid_entities:
890
+ wikidata_thing, import_warnings = wikidata_to_thing(res, all_relations=relations,
891
+ supported_locales=SUPPORTED_LOCALES,
892
+ pull_wikipedia=True,
893
+ all_wikidata_objects=qid_things)
894
+ things[res.qid] = wikidata_thing
895
+ return things
896
+
897
+
898
+ async def user_management_sample(tenant_api_key: str, instance: str) -> Tuple[User, str, str]:
899
+ """
900
+ User management sample.
901
+ Parameters
902
+ ----------
903
+ tenant_api_key: str
904
+ Session
905
+ instance: str
906
+ Instance URL
907
+
908
+ Returns
909
+ -------
910
+ user: User
911
+ User object
912
+ user_token: str
913
+ User token
914
+ refresh_token: str
915
+ Refresh token
916
+ """
917
+ user_management: AsyncUserManagementService = AsyncUserManagementService(
918
+ application_name="Async user management sample",
919
+ service_url=instance)
920
+ meta_data: dict = {'user-type': 'demo'}
921
+ user, user_token, refresh_token, _ = await user_management.create_user(tenant_key=tenant_api_key,
922
+ external_id=uuid.uuid4().hex,
923
+ meta_data=meta_data,
924
+ roles=[UserRole.USER])
925
+ return user, user_token, refresh_token
926
+
927
+
928
+ async def clean_up(instance: str, tenant_api_key: str):
929
+ """
930
+ Clean up sample.
931
+ Parameters
932
+ ----------
933
+ instance: str
934
+ Instance URL
935
+ tenant_api_key: str
936
+ Tenant API key
937
+ """
938
+ user_management: AsyncUserManagementService = AsyncUserManagementService(
939
+ application_name="Async user management sample",
940
+ service_url=instance)
941
+ users: List[User] = await user_management.listing_users(tenant_api_key)
942
+ for user in users:
943
+ if 'user-type' in user.meta_data and user.meta_data['user-type'] == 'demo':
944
+ await user_management.delete_user(tenant_key=tenant_api_key, external_id=user.external_user_id,
945
+ internal_id=user.id, force=True)
946
+
947
+
948
+ async def main(external_user_id: str, tenant_api_key: str, instance: str):
949
+ """
950
+ Main function for the async sample.
951
+
952
+ Parameters
953
+ ----------
954
+ external_user_id: str
955
+ External Id of the shadow user within the Wacom Personal Knowledge.
956
+ tenant_api_key: str
957
+ Tenant api key of the shadow user within the Wacom Personal Knowledge.
958
+ instance: str
959
+ URL of instance
960
+ """
961
+ async_client: AsyncWacomKnowledgeService = AsyncWacomKnowledgeService(application_name="Async sample",
962
+ service_url=instance)
963
+ permanent_session: PermanentSession = await async_client.login(tenant_api_key=tenant_api_key,
964
+ external_user_id=external_user_id)
965
+ """
966
+ The permanent session contains the external user id, the tenant id, thus it is capable to refresh the token and
967
+ re-login if needed. The functions check if the token is expired and refresh it if needed. Internally, the token
968
+ manager handles the session. There are three different session types:
969
+ - Permanent session: The session is refreshed automatically if needed.
970
+ - Refreshable session: The session is not refreshed automatically using the refresh token,
971
+ but if the session is not used for a day the refresh token is invalidated.
972
+ - Timed session: The session is only has the authentication token and no refresh token. Thus, it times out after
973
+ one hour.
974
+ """
975
+ print(f'Service instance: {async_client.service_url}')
976
+ print('-' * 100)
977
+ print(f'Logged in as {permanent_session.external_user_id} (tenant id: {permanent_session.tenant_id}) ')
978
+ is_ten_admin: bool = permanent_session.roles == "TenantAdmin"
979
+ print(f'Is tenant admin: {is_ten_admin}')
980
+ print('-' * 100)
981
+ print(f'Token information')
982
+ print('-' * 100)
983
+ print(f'Refreshable: {permanent_session.refreshable}')
984
+ print(f'Token must be refreshed before: {permanent_session.expiration} UTC')
985
+ print(f'Token expires in {permanent_session.expires_in} seconds)')
986
+ print('-' * 100)
987
+ print(f'Creating two users')
988
+ print('-' * 100)
989
+ # User management sample
990
+ user_1, user_token_1, refresh_token_1 = await user_management_sample(tenant_api_key, instance)
991
+ print(f'User: {user_1}')
992
+ user_2, user_token_2, refresh_token_2 = await user_management_sample(tenant_api_key, instance)
993
+ print(f'User: {user_2}')
994
+ print('-' * 100)
995
+ async_client_user_1: AsyncWacomKnowledgeService = AsyncWacomKnowledgeService(application_name="Async user 1",
996
+ service_url=instance)
997
+ refresh_session_1: RefreshableSession = await async_client_user_1.register_token(auth_key=user_token_1,
998
+ refresh_token=refresh_token_1)
999
+ async_client_user_2: AsyncWacomKnowledgeService = AsyncWacomKnowledgeService(application_name="Async sample",
1000
+ service_url=instance)
1001
+ await async_client_user_2.register_token(auth_key=user_token_2, refresh_token=refresh_token_2)
1002
+ """
1003
+ Now, let's create some entities.
1004
+ """
1005
+ print('Creation of entities')
1006
+ print('-' * 100)
1007
+ things_objects: Dict[str, ThingObject] = import_entity_from_wikidata('Leonardo da Vinci', EN)
1008
+ created: List[ThingObject] = await async_client_user_1.create_entity_bulk(list(things_objects.values()))
1009
+ for thing in created:
1010
+ try:
1011
+ await async_client_user_2.entity(thing.uri)
1012
+ except WacomServiceException as we:
1013
+ print(f'User 2 cannot see entity {thing.uri}.\n{format_exception(we)}')
1014
+
1015
+ # Now using the group management service
1016
+ group_management: AsyncGroupManagementService = AsyncGroupManagementService(application_name="Group management",
1017
+ service_url=instance)
1018
+ await group_management.use_session(refresh_session_1.id)
1019
+ # User 1 creates a group
1020
+ new_group: Group = await group_management.create_group("sample-group")
1021
+ for thing in created:
1022
+ try:
1023
+ await group_management.add_entity_to_group(new_group.id, thing.uri)
1024
+ except WacomServiceException as we:
1025
+ print(f'User 1 cannot delete entity {thing.uri}.\n{format_exception(we)}')
1026
+ await group_management.add_user_to_group(new_group.id, user_2.id)
1027
+ print(f'User 2 can see the entities now. Let us check with async client 2. '
1028
+ f'Id of the user: {async_client_user_2.current_session.external_user_id}')
1029
+ for thing in created:
1030
+ iter_thing: ThingObject = await async_client_user_2.entity(thing.uri)
1031
+ label: Optional[Label] = iter_thing.label_lang(EN_US)
1032
+ print(f'User 2 can see entity {label.content if label else "UNKNOWN"} {iter_thing.uri}.'
1033
+ f'Ownership: owner flag:={iter_thing.owner}, owner is {iter_thing.owner_id}.')
1034
+ print('-' * 100)
1035
+ await clean_up(instance=instance, tenant_api_key=tenant_api_key)
1036
+
1037
+
1038
+ if __name__ == '__main__':
1039
+ parser = argparse.ArgumentParser()
1040
+ parser.add_argument("-u", "--user", help="External Id of the shadow user within the Wacom Personal Knowledge.",
1041
+ required=True)
1042
+ parser.add_argument("-t", "--tenant", help="Tenant Id of the shadow user within the Wacom Personal Knowledge.",
1043
+ required=True)
1044
+ parser.add_argument("-i", "--instance", default='https://private-knowledge.wacom.com',
1045
+ help="URL of instance")
1046
+ args = parser.parse_args()
1047
+ asyncio.run(main(args.user, args.tenant, args.instance))
1048
+ ```
1049
+ ### Semantic Search
1050
+
1051
+ The sample shows how to use the semantic search.
1052
+ There are two types of search:
1053
+ - Label search
1054
+ - Document search
1055
+
1056
+ The label search is used to find entities based on the label.
1057
+ The document search is used to find documents based on the content.
1058
+
1059
+
1060
+ ```python
1061
+ import argparse
1062
+ import re
1063
+ import time
1064
+ from typing import List, Dict, Any
1065
+
1066
+ from knowledge.base.language import EN_US
1067
+ from knowledge.base.search import LabelMatchingResponse, DocumentSearchResponse, VectorDBDocument
1068
+ from knowledge.services.search import SemanticSearchClient
1069
+
1070
+
1071
+ def clean_text(text: str, max_length: int = -1) -> str:
1072
+ """
1073
+ Clean text from new lines and multiple spaces.
1074
+
1075
+ Parameters
1076
+ ----------
1077
+ text: str
1078
+ Text to clean.
1079
+ max_length: int [default=-1]
1080
+ Maximum length of the cleaned text. If length is - 1 then the text is not truncated.
1081
+
1082
+ Returns
1083
+ -------
1084
+ str
1085
+ Cleaned text.
1086
+ """
1087
+ # First remove new lines
1088
+ text = text.strip().replace('\n', ' ')
1089
+ # Then remove multiple spaces
1090
+ text = re.sub(r'\s+', ' ', text)
1091
+ if 0 < max_length < len(text):
1092
+ return text[:max_length] + '...'
1093
+ return text
1094
+
1095
+
1096
+ if __name__ == '__main__':
1097
+ parser = argparse.ArgumentParser()
1098
+ parser.add_argument("-u", "--user", help="External Id of the shadow user within the Wacom Personal Knowledge.",
1099
+ required=True)
1100
+ parser.add_argument("-t", "--tenant", help="Tenant Id of the shadow user within the Wacom Personal Knowledge.",
1101
+ required=True)
1102
+ parser.add_argument("-i", "--instance", default="https://private-knowledge.wacom.com", help="URL of instance")
1103
+ args = parser.parse_args()
1104
+ client: SemanticSearchClient = SemanticSearchClient(service_url=args.instance)
1105
+ session = client.login(args.tenant, args.user)
1106
+ max_results: int = 10
1107
+ labels_count: int = client.count_documents(locale=EN_US)
1108
+ print(f"Tenant ID: {client.current_session.tenant_id} | Labels count: {labels_count} for [locale:={EN_US}]")
1109
+ t0: float = time.time()
1110
+ results: LabelMatchingResponse = client.labels_search(query="Leonardo Da Vinci", locale=EN_US,
1111
+ max_results=max_results)
1112
+ t1: float = time.time()
1113
+ if len(results.results) > 0:
1114
+ print("=" * 120)
1115
+ for idx, res in enumerate(results.results):
1116
+ print(f"{idx + 1}. {res.label} | Relevance: ({res.score:.2f}) | URI: {res.entity_uri}")
1117
+ all_labels: List[VectorDBDocument] = client.retrieve_labels(EN_US, results.results[0].entity_uri)
1118
+ print("=" * 120)
1119
+ print(f"Labels for best match: {results.results[0].entity_uri}")
1120
+ for idx, label in enumerate(all_labels):
1121
+ print(f"{idx + 1}. {label.content}")
1122
+ print("=" * 120)
1123
+ print(f"Time: {(t1 - t0) * 1000:.2f} ms")
1124
+ print("=" * 120)
1125
+ document_count: int = client.count_documents(locale=EN_US)
1126
+ print(f"Document count: {document_count} for [locale:={EN_US}]")
1127
+ t2: float = time.time()
1128
+ document_results: DocumentSearchResponse = client.document_search(query="Leonardo Da Vinci artwork", locale=EN_US,
1129
+ max_results=max_results)
1130
+ t3: float = time.time()
1131
+ print("=" * 120)
1132
+ if len(document_results.results) > 0:
1133
+
1134
+ for idx, res in enumerate(document_results.results):
1135
+ print(f"{idx + 1}. URI: {res.content_uri} | Relevance: {res.score:.2f} | Chunk:"
1136
+ f"\n\t{clean_text(res.content_chunk, max_length=100)}")
1137
+ print(f"\n All document chunks for best match: {document_results.results[0].content_uri}")
1138
+ print("=" * 120)
1139
+ # If you need all document chunks, you can retrieve them using the content_uri.
1140
+ best_match_uri: str = document_results.results[0].content_uri
1141
+ chunks: List[VectorDBDocument] = client.retrieve_documents_chunks(locale=EN_US, uri=best_match_uri)
1142
+ metadata: Dict[str, Any] = document_results.results[0].metadata
1143
+ for idx, chunk in enumerate(chunks):
1144
+ print(f"{idx + 1}. {clean_text(chunk.content)}")
1145
+ print("\n\tMetadata:\n\t---------")
1146
+ for key, value in metadata.items():
1147
+ print(f"\t- {key}: {clean_text(value, max_length=100) if isinstance(value, str) else value }")
1148
+ print("=" * 120)
1149
+ print(f"Time: {(t3 - t2) * 1000:.2f} ms")
1150
+ print("=" * 120)
1151
+ ```
1152
+
1153
+ # Documentation
1154
+
1155
+ You can find more detailed technical documentation, [here](https://developer-docs.wacom.com/preview/semantic-ink/).
1156
+ API documentation is available [here](./docs/).
1157
+
1158
+ ## Contributing
1159
+ Contribution guidelines are still work in progress.
1160
+
1161
+ ## License
1162
+ [Apache License 2.0](LICENSE)
1163
+