diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index f58286e..e0f96d6 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -10,9 +10,9 @@ jobs: lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python 3.8 - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: 3.8 - name: Install dependencies @@ -24,12 +24,12 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - python-version: [3.6, 3.7, 3.8, 3.9] + python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] os: [ubuntu-latest, windows-latest] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Install dependencies @@ -40,9 +40,9 @@ jobs: check-buildable: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python - uses: actions/setup-python@v1 + uses: actions/setup-python@v4 with: python-version: '3.8' - name: Install dependencies diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f49f001..6c34c78 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -8,9 +8,9 @@ jobs: precheck: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python 3.8 - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: 3.8 - name: Install dependencies @@ -22,9 +22,9 @@ jobs: runs-on: ubuntu-latest needs: [precheck] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python - uses: actions/setup-python@v1 + uses: actions/setup-python@v4 with: python-version: '3.8' - name: Install dependencies diff --git a/CHANGELOG.md b/CHANGELOG.md index de96a0a..5d6261a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,16 @@ ## [Unreleased] +## [v0.9.3] + +**Release date: 2022-11-13** + +### Fixed + +* `wn.Synset.relations()` no longer raises a `KeyError` when no + relation types are given and relations are found via ILI ([#177]) + + ## [v0.9.2] **Release date: 2022-10-02** @@ -598,3 +608,4 @@ abandoned, but this is an entirely new codebase. [#157]: https://github.com/goodmami/wn/issues/157 [#168]: https://github.com/goodmami/wn/issues/168 [#169]: https://github.com/goodmami/wn/issues/169 +[#177]: https://github.com/goodmami/wn/issues/177 diff --git a/pyproject.toml b/pyproject.toml index 7d7245e..dd3b157 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ dynamic = ['version'] name = "wn" description = "Wordnet interface library" readme = "README.md" -requires-python = ">=3.6" +requires-python = ">=3.7" license = {file = "LICENSE"} keywords = ["wordnet", "interlingual", "linguistics", "language", "library"] authors = [ @@ -22,11 +22,11 @@ classifiers = [ "Intended Audience :: Science/Research", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", "Topic :: Scientific/Engineering :: Information Analysis", "Topic :: Software Development :: Libraries :: Python Modules", "Topic :: Text Processing :: Linguistic", diff --git a/tests/relations_test.py b/tests/relations_test.py index 1295075..088782d 100644 --- a/tests/relations_test.py +++ b/tests/relations_test.py @@ -122,3 +122,9 @@ def test_synset_relations_issue_169(): assert list(en.synset("test-en-0001-n").relations('hyponym')) == ['hyponym'] es = wn.Wordnet('test-es', expand='test-en') assert list(es.synset("test-es-0001-n").relations('hyponym')) == ['hyponym'] + + +@pytest.mark.usefixtures('mini_db') +def test_synset_relations_issue_177(): + # https://github.com/goodmami/wn/issues/177 + assert 'hyponym' in wn.synset('test-es-0001-n').relations() diff --git a/wn/__init__.py b/wn/__init__.py index 6ff5b72..7078d0e 100644 --- a/wn/__init__.py +++ b/wn/__init__.py @@ -57,4 +57,4 @@ Wordnet ) -__version__ = '0.9.2' +__version__ = '0.9.3' diff --git a/wn/_config.py b/wn/_config.py index a30e828..0827264 100644 --- a/wn/_config.py +++ b/wn/_config.py @@ -62,10 +62,10 @@ def add_project( self, id: str, type: str = _WORDNET, - label: str = None, - language: str = None, - license: str = None, - error: str = None, + label: Optional[str] = None, + language: Optional[str] = None, + license: Optional[str] = None, + error: Optional[str] = None, ) -> None: """Add a new wordnet project to the index. @@ -96,9 +96,9 @@ def add_project_version( self, id: str, version: str, - url: str = None, - error: str = None, - license: str = None, + url: Optional[str] = None, + error: Optional[str] = None, + license: Optional[str] = None, ) -> None: """Add a new resource version for a project. diff --git a/wn/_core.py b/wn/_core.py index b5c8246..2fa911d 100644 --- a/wn/_core.py +++ b/wn/_core.py @@ -94,7 +94,7 @@ def __init__( self, id: Optional[str], status: str, - definition: str = None, + definition: Optional[str] = None, _id: int = NON_ROWID, ): super().__init__(_id=_id) @@ -142,9 +142,9 @@ def __init__( email: str, license: str, version: str, - url: str = None, - citation: str = None, - logo: str = None, + url: Optional[str] = None, + citation: Optional[str] = None, + logo: Optional[str] = None, _id: int = NON_ROWID, ): super().__init__(_id=_id) @@ -249,10 +249,10 @@ class _LexiconElement(_DatabaseEntity): __slots__ = '_lexid', '_wordnet' def __init__( - self, - _lexid: int = NON_ROWID, - _id: int = NON_ROWID, - _wordnet: 'Wordnet' = None + self, + _lexid: int = NON_ROWID, + _id: int = NON_ROWID, + _wordnet: Optional['Wordnet'] = None ): super().__init__(_id=_id) self._lexid = _lexid # Database-internal lexicon id @@ -282,10 +282,10 @@ class Pronunciation: def __init__( self, value: str, - variety: str = None, - notation: str = None, + variety: Optional[str] = None, + notation: Optional[str] = None, phonemic: bool = True, - audio: str = None, + audio: Optional[str] = None, ): self.value = value self.variety = variety @@ -321,8 +321,8 @@ class Form(str): def __new__( cls, form: str, - id: str = None, - script: str = None, + id: Optional[str] = None, + script: Optional[str] = None, _id: int = NON_ROWID ): obj = str.__new__(cls, form) # type: ignore @@ -354,13 +354,13 @@ class Word(_LexiconElement): _ENTITY_TYPE = 'entries' def __init__( - self, - id: str, - pos: str, - forms: List[Tuple[str, Optional[str], Optional[str], int]], - _lexid: int = NON_ROWID, - _id: int = NON_ROWID, - _wordnet: 'Wordnet' = None + self, + id: str, + pos: str, + forms: List[Tuple[str, Optional[str], Optional[str], int]], + _lexid: int = NON_ROWID, + _id: int = NON_ROWID, + _wordnet: Optional['Wordnet'] = None ): super().__init__(_lexid=_lexid, _id=_id, _wordnet=_wordnet) self.id = id @@ -434,7 +434,7 @@ def derived_words(self) -> List['Word']: for derived_sense in sense.get_related('derivation')] def translate( - self, lexicon: str = None, *, lang: str = None, + self, lexicon: Optional[str] = None, *, lang: Optional[str] = None, ) -> Dict['Sense', List['Word']]: """Return a mapping of word senses to lists of translated words. @@ -467,11 +467,11 @@ class _Relatable(_LexiconElement): __slots__ = 'id', def __init__( - self, - id: str, - _lexid: int = NON_ROWID, - _id: int = NON_ROWID, - _wordnet: 'Wordnet' = None + self, + id: str, + _lexid: int = NON_ROWID, + _id: int = NON_ROWID, + _wordnet: Optional['Wordnet'] = None ): super().__init__(_lexid=_lexid, _id=_id, _wordnet=_wordnet) self.id = id @@ -492,7 +492,11 @@ def closure(self: T, *args: str) -> Iterator[T]: yield relatable queue.extend(relatable.get_related(*args)) - def relation_paths(self: T, *args: str, end: T = None) -> Iterator[List[T]]: + def relation_paths( + self: T, + *args: str, + end: Optional[T] = None + ) -> Iterator[List[T]]: agenda: List[Tuple[List[T], Set[T]]] = [ ([target], {self, target}) for target in self.get_related(*args) @@ -522,13 +526,13 @@ class Synset(_Relatable): _ENTITY_TYPE = 'synsets' def __init__( - self, - id: str, - pos: str, - ili: str = None, - _lexid: int = NON_ROWID, - _id: int = NON_ROWID, - _wordnet: 'Wordnet' = None + self, + id: str, + pos: str, + ili: Optional[str] = None, + _lexid: int = NON_ROWID, + _id: int = NON_ROWID, + _wordnet: Optional['Wordnet'] = None ): super().__init__(id=id, _lexid=_lexid, _id=_id, _wordnet=_wordnet) self.pos = pos @@ -536,11 +540,11 @@ def __init__( @classmethod def empty( - cls, - id: str, - ili: str = None, - _lexid: int = NON_ROWID, - _wordnet: 'Wordnet' = None + cls, + id: str, + ili: Optional[str] = None, + _lexid: int = NON_ROWID, + _wordnet: Optional['Wordnet'] = None ): return cls(id, pos='', ili=ili, _lexid=_lexid, _wordnet=_wordnet) @@ -706,11 +710,11 @@ def _get_relations(self, args: Sequence[str]) -> List[Tuple[str, 'Synset']]: # get expanded relation expss = find_synsets(ili=self._ili, lexicon_rowids=expids) rowids = {rowid for _, _, _, _, rowid in expss} - {self._id, NON_ROWID} - relations: Dict[str, Set[str]] = {reltype: set() for reltype in args} + relations: Dict[str, Set[str]] = {} for rel_row in get_synset_relations(rowids, args, expids): rel_type, ili = rel_row[0], rel_row[4] if ili is not None: - relations[rel_type].add(ili) + relations.setdefault(rel_type, set()).add(ili) # map back to target lexicons seen = {ss._id for _, ss in targets} @@ -828,7 +832,12 @@ def hyponyms(self) -> List['Synset']: 'instance_hyponym' ) - def translate(self, lexicon: str = None, *, lang: str = None) -> List['Synset']: + def translate( + self, + lexicon: Optional[str] = None, + *, + lang: Optional[str] = None + ) -> List['Synset']: """Return a list of translated synsets. Arguments: @@ -873,13 +882,13 @@ class Sense(_Relatable): _ENTITY_TYPE = 'senses' def __init__( - self, - id: str, - entry_id: str, - synset_id: str, - _lexid: int = NON_ROWID, - _id: int = NON_ROWID, - _wordnet: 'Wordnet' = None + self, + id: str, + entry_id: str, + synset_id: str, + _lexid: int = NON_ROWID, + _id: int = NON_ROWID, + _wordnet: Optional['Wordnet'] = None ): super().__init__(id=id, _lexid=_lexid, _id=_id, _wordnet=_wordnet) self._entry_id = entry_id @@ -1002,7 +1011,12 @@ def get_related_synsets(self, *args: str) -> List[Synset]: for _, _, ssid, pos, ili, lexid, rowid in iterable if lexids is None or lexid in lexids] - def translate(self, lexicon: str = None, *, lang: str = None) -> List['Sense']: + def translate( + self, + lexicon: Optional[str] = None, + *, + lang: Optional[str] = None + ) -> List['Sense']: """Return a list of translated senses. Arguments: @@ -1087,10 +1101,10 @@ class Wordnet: def __init__( self, - lexicon: str = None, + lexicon: Optional[str] = None, *, - lang: str = None, - expand: str = None, + lang: Optional[str] = None, + expand: Optional[str] = None, normalizer: Optional[NormalizeFunction] = normalize_form, lemmatizer: Optional[LemmatizeFunction] = None, search_all_forms: bool = True, @@ -1148,7 +1162,11 @@ def word(self, id: str) -> Word: except StopIteration: raise wn.Error(f'no such lexical entry: {id}') - def words(self, form: str = None, pos: str = None) -> List[Word]: + def words( + self, + form: Optional[str] = None, + pos: Optional[str] = None + ) -> List[Word]: """Return the list of matching words in this wordnet. Without any arguments, this function returns all words in the @@ -1168,7 +1186,10 @@ def synset(self, id: str) -> Synset: raise wn.Error(f'no such synset: {id}') def synsets( - self, form: str = None, pos: str = None, ili: str = None + self, + form: Optional[str] = None, + pos: Optional[str] = None, + ili: Optional[str] = None ) -> List[Synset]: """Return the list of matching synsets in this wordnet. @@ -1191,7 +1212,11 @@ def sense(self, id: str) -> Sense: except StopIteration: raise wn.Error(f'no such sense: {id}') - def senses(self, form: str = None, pos: str = None) -> List[Sense]: + def senses( + self, + form: Optional[str] = None, + pos: Optional[str] = None + ) -> List[Sense]: """Return the list of matching senses in this wordnet. Without any arguments, this function returns all senses in the @@ -1210,7 +1235,7 @@ def ili(self, id: str) -> ILI: except StopIteration: raise wn.Error(f'no such ILI: {id}') - def ilis(self, status: str = None) -> List[ILI]: + def ilis(self, status: Optional[str] = None) -> List[ILI]: """Return the list of ILIs in this wordnet. If *status* is given, only return ILIs with a matching status. @@ -1270,7 +1295,7 @@ def _find_helper( query_func: Callable, form: Optional[str], pos: Optional[str], - ili: str = None + ili: Optional[str] = None ) -> List[C]: """Return the list of matching wordnet entities. @@ -1355,7 +1380,11 @@ def projects() -> List[Dict]: ] -def lexicons(*, lexicon: str = None, lang: str = None) -> List[Lexicon]: +def lexicons( + *, + lexicon: Optional[str] = None, + lang: Optional[str] = None +) -> List[Lexicon]: """Return the lexicons matching a language or lexicon specifier. Example: @@ -1372,7 +1401,12 @@ def lexicons(*, lexicon: str = None, lang: str = None) -> List[Lexicon]: return w.lexicons() -def word(id: str, *, lexicon: str = None, lang: str = None) -> Word: +def word( + id: str, + *, + lexicon: Optional[str] = None, + lang: Optional[str] = None +) -> Word: """Return the word with *id* in *lexicon*. This will create a :class:`Wordnet` object using the *lang* and @@ -1387,11 +1421,11 @@ def word(id: str, *, lexicon: str = None, lang: str = None) -> Word: def words( - form: str = None, - pos: str = None, + form: Optional[str] = None, + pos: Optional[str] = None, *, - lexicon: str = None, - lang: str = None, + lexicon: Optional[str] = None, + lang: Optional[str] = None, ) -> List[Word]: """Return the list of matching words. @@ -1410,7 +1444,12 @@ def words( return Wordnet(lang=lang, lexicon=lexicon).words(form=form, pos=pos) -def synset(id: str, *, lexicon: str = None, lang: str = None) -> Synset: +def synset( + id: str, + *, + lexicon: Optional[str] = None, + lang: Optional[str] = None +) -> Synset: """Return the synset with *id* in *lexicon*. This will create a :class:`Wordnet` object using the *lang* and @@ -1425,12 +1464,12 @@ def synset(id: str, *, lexicon: str = None, lang: str = None) -> Synset: def synsets( - form: str = None, - pos: str = None, - ili: str = None, + form: Optional[str] = None, + pos: Optional[str] = None, + ili: Optional[str] = None, *, - lexicon: str = None, - lang: str = None, + lexicon: Optional[str] = None, + lang: Optional[str] = None, ) -> List[Synset]: """Return the list of matching synsets. @@ -1448,11 +1487,11 @@ def synsets( def senses( - form: str = None, - pos: str = None, + form: Optional[str] = None, + pos: Optional[str] = None, *, - lexicon: str = None, - lang: str = None, + lexicon: Optional[str] = None, + lang: Optional[str] = None, ) -> List[Sense]: """Return the list of matching senses. @@ -1469,7 +1508,12 @@ def senses( return Wordnet(lang=lang, lexicon=lexicon).senses(form=form, pos=pos) -def sense(id: str, *, lexicon: str = None, lang: str = None) -> Sense: +def sense( + id: str, + *, + lexicon: Optional[str] = None, + lang: Optional[str] = None +) -> Sense: """Return the sense with *id* in *lexicon*. This will create a :class:`Wordnet` object using the *lang* and @@ -1483,7 +1527,12 @@ def sense(id: str, *, lexicon: str = None, lang: str = None) -> Sense: return Wordnet(lang=lang, lexicon=lexicon).sense(id=id) -def ili(id: str, *, lexicon: str = None, lang: str = None) -> ILI: +def ili( + id: str, + *, + lexicon: Optional[str] = None, + lang: Optional[str] = None +) -> ILI: """Return the interlingual index with *id*. This will create a :class:`Wordnet` object using the *lang* and @@ -1500,10 +1549,10 @@ def ili(id: str, *, lexicon: str = None, lang: str = None) -> ILI: def ilis( - status: str = None, + status: Optional[str] = None, *, - lexicon: str = None, - lang: str = None, + lexicon: Optional[str] = None, + lang: Optional[str] = None, ) -> List[ILI]: """Return the list of matching interlingual indices. diff --git a/wn/_queries.py b/wn/_queries.py index 60c0058..71171d6 100644 --- a/wn/_queries.py +++ b/wn/_queries.py @@ -81,7 +81,7 @@ def find_lexicons( lexicon: str, - lang: str = None, + lang: Optional[str] = None, ) -> Iterator[_Lexicon]: cur = connect().cursor() found = False @@ -175,9 +175,9 @@ def get_lexicon_extensions(rowid: int, depth: int = -1) -> List[int]: def find_ilis( - id: str = None, - status: str = None, - lexicon_rowids: Sequence[int] = None, + id: Optional[str] = None, + status: Optional[str] = None, + lexicon_rowids: Sequence[int] = (), ) -> Iterator[_ILI]: if status != 'proposed': yield from _find_existing_ilis( @@ -188,9 +188,9 @@ def find_ilis( def _find_existing_ilis( - id: str = None, - status: str = None, - lexicon_rowids: Sequence[int] = None, + id: Optional[str] = None, + status: Optional[str] = None, + lexicon_rowids: Sequence[int] = (), ) -> Iterator[_ILI]: query = ''' SELECT DISTINCT i.id, ist.status, i.definition, i.rowid @@ -219,8 +219,8 @@ def _find_existing_ilis( def find_proposed_ilis( - synset_rowid: int = None, - lexicon_rowids: Sequence[int] = None, + synset_rowid: Optional[int] = None, + lexicon_rowids: Sequence[int] = (), ) -> Iterator[_ILI]: query = ''' SELECT null, "proposed", definition, rowid @@ -244,10 +244,10 @@ def find_proposed_ilis( def find_entries( - id: str = None, - forms: Sequence[str] = None, - pos: str = None, - lexicon_rowids: Sequence[int] = None, + id: Optional[str] = None, + forms: Sequence[str] = (), + pos: Optional[str] = None, + lexicon_rowids: Sequence[int] = (), normalized: bool = False, search_all_forms: bool = False, ) -> Iterator[_Word]: @@ -301,10 +301,10 @@ def find_entries( def find_senses( - id: str = None, - forms: Sequence[str] = None, - pos: str = None, - lexicon_rowids: Sequence[int] = None, + id: Optional[str] = None, + forms: Sequence[str] = (), + pos: Optional[str] = None, + lexicon_rowids: Sequence[int] = (), normalized: bool = False, search_all_forms: bool = False, ) -> Iterator[_Sense]: @@ -351,11 +351,11 @@ def find_senses( def find_synsets( - id: str = None, - forms: Sequence[str] = None, - pos: str = None, - ili: str = None, - lexicon_rowids: Sequence[int] = None, + id: Optional[str] = None, + forms: Sequence[str] = (), + pos: Optional[str] = None, + ili: Optional[str] = None, + lexicon_rowids: Sequence[int] = (), normalized: bool = False, search_all_forms: bool = False, ) -> Iterator[_Synset]: @@ -503,8 +503,8 @@ def get_examples( def find_syntactic_behaviours( - id: str = None, - lexicon_rowids: Sequence[int] = None, + id: Optional[str] = None, + lexicon_rowids: Sequence[int] = (), ) -> Iterator[_SyntacticBehaviour]: conn = connect() query = '''