Repository: LeMyst/WikibaseIntegrator

Issue:
Feature request: Add helper method for snaktype
As a user I want to indicate directly when instantiating a claim that it has no-value or unknown-value.

Discussion:

Patch:
diff --git a/notebooks/item_create_new.ipynb b/notebooks/item_create_new.ipynb
index d498f68..7ad47bd 100644
--- a/notebooks/item_create_new.ipynb
+++ b/notebooks/item_create_new.ipynb
@@ -35,12 +35,12 @@
    },
    "outputs": [],
    "source": [
-    "from wikibaseintegrator.models import Qualifiers, References, Reference\n",
-    "\n",
-    "from wikibaseintegrator import WikibaseIntegrator\n",
-    "from wikibaseintegrator import wbi_login\n",
     "from wikibaseintegrator import datatypes\n",
-    "from wikibaseintegrator.wbi_config import config"
+    "from wikibaseintegrator import wbi_login\n",
+    "from wikibaseintegrator import WikibaseIntegrator\n",
+    "from wikibaseintegrator.models import Qualifiers, Reference, References\n",
+    "from wikibaseintegrator.wbi_config import config\n",
+    "from wikibaseintegrator.wbi_enums import WikibaseRank, WikibaseSnakType"
    ]
   },
   {
@@ -162,7 +162,7 @@
    "outputs": [
     {
      "data": {
-      "text/plain": "<LanguageValue @ed5b70 _LanguageValue__language='fr' _LanguageValue__value='Nouvel élément' _LanguageValue__removed=False>"
+      "text/plain": "<LanguageValue @8e3ca0 _LanguageValue__language='fr' _LanguageValue__value='Nouvel élément' _LanguageValue__removed=False>"
      },
      "execution_count": 6,
      "metadata": {},
@@ -198,7 +198,7 @@
    "outputs": [
     {
      "data": {
-      "text/plain": "<Aliases @ed4880 _Aliases__aliases={'en': [<Alias @ed56c0 _LanguageValue__language='en' _LanguageValue__value='Item' _LanguageValue__removed=False>], 'fr': [<Alias @ed5ae0 _LanguageValue__language='fr' _LanguageValue__value='Élément' _LanguageValue__removed=False>]}>"
+      "text/plain": "<Aliases @c234c0 _Aliases__aliases={'en': [<Alias @c5ce50 _LanguageValue__language='en' _LanguageValue__value='Item' _LanguageValue__removed=False>], 'fr': [<Alias @c5cca0 _LanguageValue__language='fr' _LanguageValue__value='Élément' _LanguageValue__removed=False>]}>"
      },
      "execution_count": 7,
      "metadata": {},
@@ -234,7 +234,7 @@
    "outputs": [
     {
      "data": {
-      "text/plain": "<LanguageValue @ed5750 _LanguageValue__language='fr' _LanguageValue__value='Un élément fraichement créé' _LanguageValue__removed=False>"
+      "text/plain": "<LanguageValue @c5d240 _LanguageValue__language='fr' _LanguageValue__value='Un élément fraichement créé' _LanguageValue__removed=False>"
      },
      "execution_count": 8,
      "metadata": {},
@@ -270,7 +270,7 @@
    "outputs": [
     {
      "data": {
-      "text/plain": "<Claims @ed4460 _Claims__claims={'P31533': [<String @ed5ab0 _Claim__mainsnak=<Snak @ed6920 _Snak__snaktype=<WikibaseSnakType.KNOWN_VALUE: 'value'> _Snak__property_number='P31533' _Snak__hash=None _Snak__datavalue={'value': 'A String property', 'type': 'string'} _Snak__datatype='string'> _Claim__type='statement' _Claim__qualifiers=<Qualifiers @ed7370 _Qualifiers__qualifiers={'P828': [<Snak @ed6980 _Snak__snaktype=<WikibaseSnakType.KNOWN_VALUE: 'value'> _Snak__property_number='P828' _Snak__hash=None _Snak__datavalue={'value': 'Item qualifier', 'type': 'string'} _Snak__datatype='string'>]}> _Claim__qualifiers_order=[] _Claim__id=None _Claim__rank=<WikibaseRank.NORMAL: 'normal'> _Claim__removed=False _Claim__references=<References @ed5ff0 _References__references=[<Reference @ed6260 _Reference__hash=None _Reference__snaks=<Snaks @ed68c0 snaks={'P828': [<Snak @ed6aa0 _Snak__snaktype=<WikibaseSnakType.KNOWN_VALUE: 'value'> _Snak__property_number='P828' _Snak__hash=None _Snak__datavalue={'value': 'Item string reference', 'type': 'string'} _Snak__datatype='string'>]}> _Reference__snaks_order=[]>, <Reference @ed69e0 _Reference__hash=None _Reference__snaks=<Snaks @ed6950 snaks={'P828': [<Snak @ed7c40 _Snak__snaktype=<WikibaseSnakType.KNOWN_VALUE: 'value'> _Snak__property_number='P828' _Snak__hash=None _Snak__datavalue={'value': 'Another item string reference', 'type': 'string'} _Snak__datatype='string'>]}> _Reference__snaks_order=[]>]>>]}>"
+      "text/plain": "<Claims @c233a0 _Claims__claims={'P31533': [<String @c5e0b0 _Claim__mainsnak=<Snak @c5f700 _Snak__snaktype=<WikibaseSnakType.KNOWN_VALUE: 'value'> _Snak__property_number='P31533' _Snak__hash=None _Snak__datavalue={'value': 'A String property', 'type': 'string'} _Snak__datatype='string'> _Claim__type='statement' _Claim__qualifiers=<Qualifiers @c5f820 _Qualifiers__qualifiers={'P828': [<Snak @c5dc00 _Snak__snaktype=<WikibaseSnakType.KNOWN_VALUE: 'value'> _Snak__property_number='P828' _Snak__hash=None _Snak__datavalue={'value': 'Item qualifier', 'type': 'string'} _Snak__datatype='string'>]}> _Claim__qualifiers_order=[] _Claim__id=None _Claim__rank=<WikibaseRank.PREFERRED: 'preferred'> _Claim__removed=False _Claim__references=<References @c5e140 _References__references=[<Reference @c5fa00 _Reference__hash=None _Reference__snaks=<Snaks @c5da50 snaks={'P828': [<Snak @c5dd20 _Snak__snaktype=<WikibaseSnakType.KNOWN_VALUE: 'value'> _Snak__property_number='P828' _Snak__hash=None _Snak__datavalue={'value': 'Item string reference', 'type': 'string'} _Snak__datatype='string'>]}> _Reference__snaks_order=[]>, <Reference @c5dc60 _Reference__hash=None _Reference__snaks=<Snaks @c5dbd0 snaks={'P828': [<Snak @c5f5b0 _Snak__snaktype=<WikibaseSnakType.KNOWN_VALUE: 'value'> _Snak__property_number='P828' _Snak__hash=None _Snak__datavalue={'value': 'Another item string reference', 'type': 'string'} _Snak__datatype='string'>]}> _Reference__snaks_order=[]>, <Reference @c5e0e0 _Reference__hash=None _Reference__snaks=<Snaks @c5dba0 snaks={'P828': [<Snak @c5f730 _Snak__snaktype=<WikibaseSnakType.NO_VALUE: 'novalue'> _Snak__property_number='P828' _Snak__hash=None _Snak__datavalue={} _Snak__datatype='string'>]}> _Reference__snaks_order=[]>]>>], 'P3133': [<String @c5f580 _Claim__mainsnak=<Snak @c5f6d0 _Snak__snaktype=<WikibaseSnakType.UNKNOWN_VALUE: 'somevalue'> _Snak__property_number='P3133' _Snak__hash=None _Snak__datavalue={} _Snak__datatype='string'> _Claim__type='statement' _Claim__qualifiers=<Qualifiers @c5f610 _Qualifiers__qualifiers={}> _Claim__qualifiers_order=[] _Claim__id=None _Claim__rank=<WikibaseRank.NORMAL: 'normal'> _Claim__removed=False _Claim__references=<References @c5f7c0 _References__references=[]>>]}>"
      },
      "execution_count": 9,
      "metadata": {},
@@ -282,18 +282,34 @@
     "new_qualifiers.add(datatypes.String(prop_nr='P828', value='Item qualifier'))\n",
     "\n",
     "new_references = References()\n",
+    "\n",
+    "# Create a first reference\n",
     "new_reference1 = Reference()\n",
     "new_reference1.add(datatypes.String(prop_nr='P828', value='Item string reference'))\n",
     "\n",
+    "# Create another reference\n",
     "new_reference2 = Reference()\n",
     "new_reference2.add(datatypes.String(prop_nr='P828', value='Another item string reference'))\n",
     "\n",
+    "# Create a reference with \"no value\"\n",
+    "new_reference3 = Reference()\n",
+    "new_reference3.add(datatypes.String(prop_nr='P828', snaktype=WikibaseSnakType.NO_VALUE))\n",
+    "\n",
+    "# Add all the references to the References object\n",
     "new_references.add(new_reference1)\n",
     "new_references.add(new_reference2)\n",
+    "new_references.add(new_reference3)\n",
+    "\n",
+    "# Create the claim with the qualifiers and refererences. Set rank as 'preferred'.\n",
+    "new_claim = datatypes.String(prop_nr='P31533', value='A String property', qualifiers=new_qualifiers,\n",
+    "                             references=new_references, rank=WikibaseRank.PREFERRED)\n",
+    "\n",
+    "new_item.claims.add(new_claim)\n",
     "\n",
-    "new_claim = datatypes.String(prop_nr='P31533', value='A String property', qualifiers=new_qualifiers, references=new_references)\n",
+    "# Create a claim with an unknown value\n",
+    "unknown_claim = datatypes.String(prop_nr='P3133', snaktype=WikibaseSnakType.UNKNOWN_VALUE)\n",
     "\n",
-    "new_item.claims.add(new_claim)"
+    "new_item.claims.add(unknown_claim)"
    ]
   },
   {
@@ -320,7 +336,7 @@
    "outputs": [
     {
      "data": {
-      "text/plain": "<ItemEntity @ed4640 _BaseEntity__api=<wikibaseintegrator.wikibaseintegrator.WikibaseIntegrator object at 0x000001D8C4ED42E0>\n\t _BaseEntity__title=None\n\t _BaseEntity__pageid=None\n\t _BaseEntity__lastrevid=579081\n\t _BaseEntity__type='item'\n\t _BaseEntity__id='Q225256'\n\t _BaseEntity__claims=<Claims @ed79a0 _Claims__claims={'P31533': [<String @ed7550 _Claim__mainsnak=<Snak @ed7160 _Snak__snaktype=<WikibaseSnakType.KNOWN_VALUE: 'value'> _Snak__property_number='P31533' _Snak__hash='112d32b098a091cc1398c779e76c763a523d4ffc' _Snak__datavalue={'value': 'A String property', 'type': 'string'} _Snak__datatype='string'> _Claim__type='statement' _Claim__qualifiers=<Qualifiers @ed6fb0 _Qualifiers__qualifiers={'P828': [<Snak @ed71c0 _Snak__snaktype=<WikibaseSnakType.KNOWN_VALUE: 'value'> _Snak__property_number='P828' _Snak__hash='8d721edd0365e35ed006822601a4837b35e68fd6' _Snak__datavalue={'value': 'Item qualifier', 'type': 'string'} _Snak__datatype='string'>]}> _Claim__qualifiers_order=['P828'] _Claim__id='Q225256$A1CB5069-5FF4-4EE4-BE99-D1607BFFB705' _Claim__rank=<WikibaseRank.NORMAL: 'normal'> _Claim__removed=False _Claim__references=<References @ed7010 _References__references=[<Reference @ed75b0 _Reference__hash='9820f3e32182f8b5575be8b9cf55b9c7e5fbf269' _Reference__snaks=<Snaks @ed6f20 snaks={'P828': [<Snak @ed7220 _Snak__snaktype=<WikibaseSnakType.KNOWN_VALUE: 'value'> _Snak__property_number='P828' _Snak__hash='811577f0f42a7059f39bd6b169366bb1fb2f9af3' _Snak__datavalue={'value': 'Item string reference', 'type': 'string'} _Snak__datatype='string'>]}> _Reference__snaks_order=['P828']>, <Reference @ed76a0 _Reference__hash='0d2ff45b3eace5dd184ad5f4ac0d1c6eff35e4ac' _Reference__snaks=<Snaks @ed7490 snaks={'P828': [<Snak @ed7580 _Snak__snaktype=<WikibaseSnakType.KNOWN_VALUE: 'value'> _Snak__property_number='P828' _Snak__hash='774c2b3d70f072fb26d05a95d24445fbc8b2534e' _Snak__datavalue={'value': 'Another item string reference', 'type': 'string'} _Snak__datatype='string'>]}> _Reference__snaks_order=['P828']>]>>]}>\n\t _ItemEntity__labels=<Labels @ed74f0 _LanguageValues__values={'en': <LanguageValue @ed7130 _LanguageValue__language='en' _LanguageValue__value='New item' _LanguageValue__removed=False>, 'fr': <LanguageValue @ed7190 _LanguageValue__language='fr' _LanguageValue__value='Nouvel élément' _LanguageValue__removed=False>}>\n\t _ItemEntity__descriptions=<Descriptions @ed4280 _LanguageValues__values={'en': <LanguageValue @ed54e0 _LanguageValue__language='en' _LanguageValue__value='A freshly created element' _LanguageValue__removed=False>, 'fr': <LanguageValue @ed7040 _LanguageValue__language='fr' _LanguageValue__value='Un élément fraichement créé' _LanguageValue__removed=False>}>\n\t _ItemEntity__aliases=<Aliases @ed4760 _Aliases__aliases={'en': [<Alias @ed6e60 _LanguageValue__language='en' _LanguageValue__value='Item' _LanguageValue__removed=False>], 'fr': [<Alias @ed76d0 _LanguageValue__language='fr' _LanguageValue__value='Élément' _LanguageValue__removed=False>]}>\n\t _ItemEntity__sitelinks=<Sitelinks @ed6350 sitelinks={}>>"
+      "text/plain": "<ItemEntity @c23520 _BaseEntity__api=<wikibaseintegrator.wikibaseintegrator.WikibaseIntegrator object at 0x0000024546C23400>\n\t _BaseEntity__title=None\n\t _BaseEntity__pageid=None\n\t _BaseEntity__lastrevid=598021\n\t _BaseEntity__type='item'\n\t _BaseEntity__id='Q226304'\n\t _BaseEntity__claims=<Claims @c5f0d0 _Claims__claims={'P31533': [<String @c5fee0 _Claim__mainsnak=<Snak @c5fe20 _Snak__snaktype=<WikibaseSnakType.KNOWN_VALUE: 'value'> _Snak__property_number='P31533' _Snak__hash='112d32b098a091cc1398c779e76c763a523d4ffc' _Snak__datavalue={'value': 'A String property', 'type': 'string'} _Snak__datatype='string'> _Claim__type='statement' _Claim__qualifiers=<Qualifiers @c5ef80 _Qualifiers__qualifiers={'P828': [<Snak @c5fca0 _Snak__snaktype=<WikibaseSnakType.KNOWN_VALUE: 'value'> _Snak__property_number='P828' _Snak__hash='8d721edd0365e35ed006822601a4837b35e68fd6' _Snak__datavalue={'value': 'Item qualifier', 'type': 'string'} _Snak__datatype='string'>]}> _Claim__qualifiers_order=['P828'] _Claim__id='Q226304$C318B066-FD5E-4766-BD03-5F881145511A' _Claim__rank=<WikibaseRank.PREFERRED: 'preferred'> _Claim__removed=False _Claim__references=<References @c5efb0 _References__references=[<Reference @c5d900 _Reference__hash='9820f3e32182f8b5575be8b9cf55b9c7e5fbf269' _Reference__snaks=<Snaks @c5fd30 snaks={'P828': [<Snak @c5d720 _Snak__snaktype=<WikibaseSnakType.KNOWN_VALUE: 'value'> _Snak__property_number='P828' _Snak__hash='811577f0f42a7059f39bd6b169366bb1fb2f9af3' _Snak__datavalue={'value': 'Item string reference', 'type': 'string'} _Snak__datatype='string'>]}> _Reference__snaks_order=['P828']>, <Reference @c5f340 _Reference__hash='0d2ff45b3eace5dd184ad5f4ac0d1c6eff35e4ac' _Reference__snaks=<Snaks @c5f280 snaks={'P828': [<Snak @c5d3f0 _Snak__snaktype=<WikibaseSnakType.KNOWN_VALUE: 'value'> _Snak__property_number='P828' _Snak__hash='774c2b3d70f072fb26d05a95d24445fbc8b2534e' _Snak__datavalue={'value': 'Another item string reference', 'type': 'string'} _Snak__datatype='string'>]}> _Reference__snaks_order=['P828']>, <Reference @c5e290 _Reference__hash='4968e32f26488317c52a8883b49cb160b39e3428' _Reference__snaks=<Snaks @c5ffd0 snaks={'P828': [<Snak @c5c7c0 _Snak__snaktype=<WikibaseSnakType.NO_VALUE: 'novalue'> _Snak__property_number='P828' _Snak__hash='6e63dffef5a685b86c63dafda7a4748cbe8b029e' _Snak__datavalue={} _Snak__datatype='string'>]}> _Reference__snaks_order=['P828']>]>>], 'P3133': [<MonolingualText @c5f520 _Claim__mainsnak=<Snak @c5d060 _Snak__snaktype=<WikibaseSnakType.UNKNOWN_VALUE: 'somevalue'> _Snak__property_number='P3133' _Snak__hash='4b66bd689df0c4cd59c2df014b4e6a97ee99240d' _Snak__datavalue={} _Snak__datatype='monolingualtext'> _Claim__type='statement' _Claim__qualifiers=<Qualifiers @c5ef50 _Qualifiers__qualifiers={}> _Claim__qualifiers_order=[] _Claim__id='Q226304$7B072F85-CDB5-4F8D-9F34-ABDE829581FC' _Claim__rank=<WikibaseRank.NORMAL: 'normal'> _Claim__removed=False _Claim__references=<References @c5feb0 _References__references=[]>>]}>\n\t _ItemEntity__labels=<Labels @c5ff10 _LanguageValues__values={'en': <LanguageValue @c5fdf0 _LanguageValue__language='en' _LanguageValue__value='New item' _LanguageValue__removed=False>, 'fr': <LanguageValue @c5c5b0 _LanguageValue__language='fr' _LanguageValue__value='Nouvel élément' _LanguageValue__removed=False>}>\n\t _ItemEntity__descriptions=<Descriptions @c234f0 _LanguageValues__values={'en': <LanguageValue @c5cfd0 _LanguageValue__language='en' _LanguageValue__value='A freshly created element' _LanguageValue__removed=False>, 'fr': <LanguageValue @c5eda0 _LanguageValue__language='fr' _LanguageValue__value='Un élément fraichement créé' _LanguageValue__removed=False>}>\n\t _ItemEntity__aliases=<Aliases @c23550 _Aliases__aliases={'en': [<Alias @c5ef20 _LanguageValue__language='en' _LanguageValue__value='Item' _LanguageValue__removed=False>], 'fr': [<Alias @c5ed10 _LanguageValue__language='fr' _LanguageValue__value='Élément' _LanguageValue__removed=False>]}>\n\t _ItemEntity__sitelinks=<Sitelinks @c5d2a0 sitelinks={}>>"
      },
      "execution_count": 10,
      "metadata": {},
diff --git a/wikibaseintegrator/models/claims.py b/wikibaseintegrator/models/claims.py
index 641ae1e..8a64564 100644
--- a/wikibaseintegrator/models/claims.py
+++ b/wikibaseintegrator/models/claims.py
@@ -8,7 +8,7 @@ from wikibaseintegrator.models.basemodel import BaseModel
 from wikibaseintegrator.models.qualifiers import Qualifiers
 from wikibaseintegrator.models.references import Reference, References
 from wikibaseintegrator.models.snaks import Snak, Snaks
-from wikibaseintegrator.wbi_enums import ActionIfExists, WikibaseRank
+from wikibaseintegrator.wbi_enums import ActionIfExists, WikibaseRank, WikibaseSnakType
 
 
 class Claims(BaseModel):
@@ -131,14 +131,15 @@ class Claims(BaseModel):
 class Claim(BaseModel):
     DTYPE = 'claim'
 
-    def __init__(self, qualifiers: Optional[Qualifiers] = None, rank: Optional[WikibaseRank] = None, references: Optional[Union[References, List[Union[Claim, List[Claim]]]]] = None) -> None:
+    def __init__(self, qualifiers: Optional[Qualifiers] = None, rank: Optional[WikibaseRank] = None, references: Optional[Union[References, List[Union[Claim, List[Claim]]]]] = None, snaktype: WikibaseSnakType = WikibaseSnakType.KNOWN_VALUE) -> None:
         """
 
         :param qualifiers:
         :param rank:
         :param references: A References object, a list of Claim object or a list of list of Claim object
+        :param snaktype:
         """
-        self.mainsnak = Snak(datatype=self.DTYPE)
+        self.mainsnak = Snak(datatype=self.DTYPE, snaktype=snaktype)
         self.type = 'statement'
         self.qualifiers = qualifiers or Qualifiers()
         self.qualifiers_order = []
diff --git a/wikibaseintegrator/models/snaks.py b/wikibaseintegrator/models/snaks.py
index 3d5f207..0388b7a 100644
--- a/wikibaseintegrator/models/snaks.py
+++ b/wikibaseintegrator/models/snaks.py
@@ -100,7 +100,7 @@ class Snak(BaseModel):
 
     @datavalue.setter
     def datavalue(self, value):
-        if value is not None:
+        if value is not None and value != {}:
             self.snaktype = WikibaseSnakType.KNOWN_VALUE
         self.__datavalue = value
 
diff --git a/wikibaseintegrator/wbi_helpers.py b/wikibaseintegrator/wbi_helpers.py
index 107e274..2c539b9 100644
--- a/wikibaseintegrator/wbi_helpers.py
+++ b/wikibaseintegrator/wbi_helpers.py
@@ -472,6 +472,28 @@ def search_entities(search_string: str, language: Optional[str] = None, strict_l
     return results
 
 
+def fulltext_search(search: str, max_results: int = 50, allow_anonymous: bool = True, **kwargs: Any) -> List[Dict[str, Any]]:
+    """
+    Perform a fulltext search on the mediawiki instance.
+    It's an exception to the "only wikibase related function" rule! WikibaseIntegrator is focused on wikibase-only functions to avoid spreading out and covering all functions of MediaWiki.
+
+    :param search: Search for page titles or content matching this value. You can use the search string to invoke special search features, depending on what the wiki's search backend implements.
+    :param max_results: How many total pages to return. The value must be between 1 and 500.
+    :param allow_anonymous: Allow anonymous interaction with the MediaWiki API. 'True' by default.
+    :param kwargs: Extra parameters for mediawiki_api_call_helper()
+    :return:
+    """
+    params = {
+        'action': 'query',
+        'list': 'search',
+        'srsearch': search,
+        'srlimit': max_results,
+        'format': 'json'
+    }
+
+    return mediawiki_api_call_helper(data=params, allow_anonymous=allow_anonymous, **kwargs)['query']['search']
+
+
 def generate_entity_instances(entities: Union[str, List[str]], allow_anonymous: bool = True, **kwargs: Any) -> List[Tuple[str, BaseEntity]]:
     """
     A method which allows for retrieval of a list of Wikidata entities. The method generates a list of tuples where the first value in the tuple is the entity's ID, whereas the


Test patch:
diff --git a/test/test_wbi_core.py b/test/test_wbi_core.py
index 4bb3941..74d4e9a 100644
--- a/test/test_wbi_core.py
+++ b/test/test_wbi_core.py
@@ -210,6 +210,9 @@ class TestWbiCore(unittest.TestCase):
         with self.assertRaises(ValueError):
             t4.mainsnak.snaktype = 'invalid_value'
 
+        t5 = String(prop_nr='P1', snaktype=WikibaseSnakType.NO_VALUE)
+        assert t5.mainsnak.get_json()['snaktype'] == WikibaseSnakType.NO_VALUE.value
+
     def test_new_item_creation(self):
         data = [
             String(value='test1', prop_nr='P1'),