diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..59ee47b --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,4 @@ +- [ ] I have read the [section on commits and pull requests](https://github.com/NaturalHistoryMuseum/ckanext-attribution/blob/main/CONTRIBUTING.md#commits-and-pull-requests) in `CONTRIBUTING.md` + + +Describe your changes, tagging relevant issues where possible. diff --git a/.github/workflows/bump.yml b/.github/workflows/bump.yml index 8b17261..1bb1860 100644 --- a/.github/workflows/bump.yml +++ b/.github/workflows/bump.yml @@ -7,12 +7,12 @@ on: jobs: bump-version: - if: "!startsWith(github.event.head_commit.message, 'bump:')" + name: Bump version and create changelog runs-on: ubuntu-latest - name: "Bump version and create changelog" + if: "!startsWith(github.event.head_commit.message, 'bump:')" steps: - - name: Check out - uses: actions/checkout@v3 + - name: Checkout source code + uses: actions/checkout@v4 with: token: ${{ secrets.PERSONAL_ACCESS_TOKEN }} fetch-depth: 0 diff --git a/.github/workflows/pull-requests.yml b/.github/workflows/pull-requests.yml new file mode 100644 index 0000000..b9aa2bd --- /dev/null +++ b/.github/workflows/pull-requests.yml @@ -0,0 +1,27 @@ +name: Validate pull requests + +on: + pull_request: + types: [opened, edited, reopened, synchronize] + +jobs: + validate-commits: + name: Validate commit messages + runs-on: ubuntu-latest + steps: + - name: Checkout source code + uses: actions/checkout@v4 + - name: Check commit message format + uses: webiny/action-conventional-commits@v1.3.0 + with: + allowed-commit-types: 'feat,fix,refactor,perf,docs,style,test,build,ci,chore,new,patch,revert,ui' + pre-commit: + name: Run pre-commit checks + runs-on: ubuntu-latest + steps: + - name: Checkout source code + uses: actions/checkout@v4 + - name: Setup Python + uses: actions/setup-python@v5 + - name: Run pre-commit + uses: pre-commit/action@v3.0.1 diff --git a/.github/workflows/pypi-publish.yml b/.github/workflows/pypi-publish.yml index 6866a49..d3ac620 100644 --- a/.github/workflows/pypi-publish.yml +++ b/.github/workflows/pypi-publish.yml @@ -10,17 +10,16 @@ permissions: jobs: deploy: + name: Deploy package to PyPI runs-on: ubuntu-latest steps: - - name: Check out - uses: actions/checkout@v3 + - name: Checkout source code + uses: actions/checkout@v4 with: token: ${{ secrets.PERSONAL_ACCESS_TOKEN }} fetch-depth: 0 - name: Set up Python - uses: actions/setup-python@v3 - with: - python-version: '3.x' + uses: actions/setup-python@5 - name: Install dependencies run: | python -m pip install --upgrade pip diff --git a/.github/workflows/sync.yml b/.github/workflows/sync.yml index bc66f38..4fec115 100644 --- a/.github/workflows/sync.yml +++ b/.github/workflows/sync.yml @@ -7,11 +7,11 @@ on: jobs: sync-branches: + name: Sync dev and patch branches to latest commit runs-on: ubuntu-latest - name: "Sync dev and patch branches to latest commit" steps: - - name: Check out - uses: actions/checkout@v3 + - name: Checkout source code + uses: actions/checkout@v4 with: token: ${{ secrets.PERSONAL_ACCESS_TOKEN }} fetch-depth: 0 diff --git a/.github/workflows/main.yml b/.github/workflows/tests.yml similarity index 77% rename from .github/workflows/main.yml rename to .github/workflows/tests.yml index 38530ec..faa59fa 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/tests.yml @@ -3,18 +3,18 @@ name: Tests on: push: workflow_dispatch: + pull_request: + types: [opened, edited, reopened, synchronize] jobs: test: + name: Run tests runs-on: ubuntu-latest - steps: - name: Checkout source code - uses: actions/checkout@v3 - + uses: actions/checkout@v4 - name: Build images run: docker compose build - - name: Run tests env: COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2d3b017..04e9d79 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,33 +1,34 @@ exclude: /(vendor|dist)/ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.3.0 + rev: v5.0.0 hooks: - id: check-merge-conflict - id: detect-private-key - id: end-of-file-fixer - id: name-tests-test - args: ["--pytest-test-first"] + args: ['--pytest-test-first'] exclude: ^tests/helpers/ - id: trailing-whitespace - repo: https://github.com/commitizen-tools/commitizen - rev: v2.37.0 + rev: v3.30.0 hooks: - id: commitizen - additional_dependencies: ["cz-nhm"] - - repo: https://github.com/psf/black - rev: 22.10.0 + additional_dependencies: ['cz-nhm'] + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.7.1 hooks: - - id: black + - id: ruff + args: [ '--fix', '--select', 'I', '--select', 'F401', '--fix-only' ] + - id: ruff-format - repo: https://github.com/PyCQA/docformatter - rev: v1.5.0 + rev: eb1df34 hooks: - id: docformatter - # these can't be pulled directly from the config atm, not sure why - args: ["-i", "--wrap-summaries=88", "--wrap-descriptions=88", - "--pre-summary-newline", "--make-summary-multi-line"] + args: [ '-i', '--config', './pyproject.toml' ] + additional_dependencies: ['tomli'] - repo: https://github.com/pre-commit/mirrors-prettier - rev: v3.0.0-alpha.4 + rev: v4.0.0-alpha.8 hooks: - id: prettier types_or: [ javascript, vue, less, sass, scss, css ] diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3372db8..e619a40 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -41,7 +41,7 @@ This extension and [several others](https://github.com/search?q=topic:ckan+org:N The current core team consists of: - Josh ([@jrdh](https://github.com/jrdh)) - Technical Lead -- Ginger ([@alycejenni](https://github.com/alycejenni)) - Software Engineer +- Ginger ([@alycejenni](https://github.com/alycejenni)) - Senior Software Engineer ## Questions @@ -81,7 +81,10 @@ The process is generally as follows: 3. Make your changes, and commit often; each commit should only contain one change. See below for specifics on how to word your commits. 4. Push your changes back to your fork. 5. [Open a pull request](https://docs.github.com/en/get-started/quickstart/contributing-to-projects#making-a-pull-request) in this repository, with the base branch set to **dev** and the compare branch set to your new branch. Provide a summary of your changes in the description. -6. If the automatic tests fail (these may take a while), please go back to your code and try to make them pass. You may have to update the tests themselves. You don't have to close the pull request while you're doing this; it'll update as you add further commits. +6. There are several automated checks that will run when you open the pull request. Try to make all of them pass. If you do not at least _attempt_ to make them pass, we will not merge your pull request. + 1. Tests. Update them so that they pass, if necessary. New tests are always welcome in any pull request, but if you have added a new feature that has decreased the coverage, new tests are required. + 2. Commit format validation. If you have not followed the conventional commits format for one or more of your commits, this will fail. + 3. Code format validation. If you have not formatted your code correctly (using Ruff, docformatter, and/or Prettier), this will fail. 7. Wait for feedback from one of the core maintainers. If it's been a week or so and we haven't responded, we may not have seen it. You can find other places to contact us in [SUPPORT.md](./.github/SUPPORT.md). ### Commits @@ -142,9 +145,9 @@ cz c ##### pre-commit -pre-commit is a tool that runs a variety of checks and modifications before a commit is made. You can check the [.pre-commit-config.yaml](./.pre-commit-config.yaml) file to see eaxtly what it's currently configured to do for this repository, but of particular note: +pre-commit is a tool that runs a variety of checks and modifications before a commit is made. You can check the [.pre-commit-config.yaml](./.pre-commit-config.yaml) file to see exactly what it's currently configured to do for this repository, but of particular note: -- reformats Python code with [Black](https://github.com/psf/black) +- reformats Python code with [Ruff](https://docs.astral.sh/ruff) - reformats JavaScript and stylesheets with [Prettier](https://prettier.io) - reformats docstrings with [docformatter](https://github.com/PyCQA/docformatter) - checks your commit message is correcly formatted @@ -161,15 +164,15 @@ pre-commit run Don't forget to stage any modifications that it makes! Once it runs without failing, then you can make your commit. -Something to remember is that empty docstrings will cause conflicts between Black and docformatter and the checks will fail repeatedly - so don't leave your docstrings empty! +Something to remember is that empty docstrings will cause conflicts between Ruff and docformatter and the checks will fail repeatedly - so don't leave your docstrings empty! ### Code changes and style guide -We generally use external style guides and tools to help us maintain standardised code. Black and Prettier will be run with pre-commit. +We generally use external style guides and tools to help us maintain standardised code. Ruff and Prettier will be run with pre-commit. #### Python -We follow the [Black style](https://black.readthedocs.io/en/stable/the_black_code_style/current_style.html), with the notable exception that we use single quotes. +We use [Ruff](https://docs.astral.sh/ruff) to format our code, using defaults for everything except quote style (we use single quotes). We also _mostly_ use [CKAN's style](http://docs.ckan.org/en/latest/contributing/python.html), with the following exceptions: - prefer `f''` strings over `.format()` @@ -178,7 +181,7 @@ We also _mostly_ use [CKAN's style](http://docs.ckan.org/en/latest/contributing/ #### JavaScript and stylesheets (CSS, LESS, etc) -We use [Prettier](https://prettier.io) to format these files. +We use [Prettier](https://prettier.io) to format these files. As with Ruff, we use defaults for everything except quote style (we use single quotes). #### Accessibility diff --git a/README.md b/README.md index 90dacda..d7ff7b9 100644 --- a/README.md +++ b/README.md @@ -94,6 +94,8 @@ Path variables used below: ```shell pip install ckanext-attribution +# to use the CLI as well: +pip install ckanext-attribution[cli] ``` ## Installing from source @@ -112,6 +114,8 @@ pip install ckanext-attribution 3. Install via pip: ```shell pip install $INSTALL_FOLDER/src/ckanext-attribution + # to use the cli as well: + pip install $INSTALL_FOLDER/src/ckanext-attribution[cli] ``` ### Installing in editable mode @@ -352,6 +356,8 @@ toolkit.get_action('agent_external_read')({}, data_dict) ## Commands +**NB**: you will have to install the optional `[cli]` packages to use several of these commands. + ### `initdb` ```shell ckan -c $CONFIG_FILE attribution initdb diff --git a/ckanext/attribution/commands/cli.py b/ckanext/attribution/commands/cli.py index b3cb231..d8b286c 100644 --- a/ckanext/attribution/commands/cli.py +++ b/ckanext/attribution/commands/cli.py @@ -8,6 +8,9 @@ from ckan.model import Session from ckan.model.package_extra import PackageExtra from ckan.plugins import toolkit +from fuzzywuzzy import fuzz, process +from sqlalchemy import and_, or_ + from ckanext.attribution.commands import migration from ckanext.attribution.logic.actions.helpers import get_author_string from ckanext.attribution.model import ( @@ -19,14 +22,12 @@ relationships, ) from ckanext.attribution.model.crud import ( - AgentQuery, - PackageQuery, - PackageContributionActivityQuery, AgentAffiliationQuery, AgentContributionActivityQuery, + AgentQuery, + PackageContributionActivityQuery, + PackageQuery, ) -from fuzzywuzzy import process, fuzz -from sqlalchemy import and_, or_ def get_commands(): @@ -235,7 +236,7 @@ def merge_agents(q, match_threshold): @click.option( '--limit', help='Process n packages at a time (best for testing/debugging).' ) -@click.option('--dry-run', help='Don\'t save anything to the database.', is_flag=True) +@click.option('--dry-run', help="Don't save anything to the database.", is_flag=True) @click.option( '--search-api/--no-search-api', help='Search external APIs (e.g. ORCID) for details.', diff --git a/ckanext/attribution/commands/migration/__init__.py b/ckanext/attribution/commands/migration/__init__.py index 2d5b1db..19dfe6e 100644 --- a/ckanext/attribution/commands/migration/__init__.py +++ b/ckanext/attribution/commands/migration/__init__.py @@ -1,4 +1,4 @@ -from .parser import Parser -from .common import multi_choice, rgx -from .combiner import Combiner from .api_search import APISearch +from .combiner import Combiner +from .common import multi_choice, rgx +from .parser import Parser diff --git a/ckanext/attribution/commands/migration/api_search.py b/ckanext/attribution/commands/migration/api_search.py index 199a42a..47f1fef 100644 --- a/ckanext/attribution/commands/migration/api_search.py +++ b/ckanext/attribution/commands/migration/api_search.py @@ -5,12 +5,19 @@ # Created by the Natural History Museum in London, UK import click + from ckanext.attribution.lib.orcid_api import OrcidApi from ckanext.attribution.lib.ror_api import RorApi -from prompt_toolkit import prompt -from nameparser import HumanName -from .common import multi_choice +try: + from nameparser import HumanName + from prompt_toolkit import prompt + + cli_installed = True +except ImportError: + cli_installed = False + +from .common import check_installed, multi_choice class APISearch(object): @@ -43,8 +50,10 @@ def _search_api(self, contrib, lookup, api_name, result_format): :param lookup: dict of query params (plus display_name) to send to api.search :param api_name: name of the API (see self.api) :param result_format: display format of each result, e.g. "{name} ({location})" - :return: None if not found, dict for matching result if found/selected + :returns: None if not found, dict for matching result if found/selected """ + check_installed(cli_installed) + aff = '; '.join(contrib.get('affiliations', [])) api = self.api[api_name] display_name = lookup.pop('display_name') @@ -53,7 +62,7 @@ def _search_api(self, contrib, lookup, api_name, result_format): f'Do any of these {api_name} results match "{display_name}" ({aff})?' ) click.echo(f'\nSearching {api_name} for "{display_name}"...') - results = api.search(**lookup).get(u'records', []) + results = api.search(**lookup).get('records', []) if len(results) > 0: i = multi_choice( question, @@ -94,6 +103,8 @@ def _search_api(self, contrib, lookup, api_name, result_format): return update_dict def _manual_edit(self, contrib, api_name): + check_installed(cli_installed) + if click.confirm('Enter ID manually?'): api = self.api[api_name] _id = click.prompt(f'{api_name} ID', show_default=False).strip() diff --git a/ckanext/attribution/commands/migration/combiner.py b/ckanext/attribution/commands/migration/combiner.py index 6917153..848cc3d 100644 --- a/ckanext/attribution/commands/migration/combiner.py +++ b/ckanext/attribution/commands/migration/combiner.py @@ -8,13 +8,16 @@ import re import click -from ckanext.attribution.lib.orcid_api import OrcidApi -from ckanext.attribution.lib.ror_api import RorApi from fuzzywuzzy import process -from prompt_toolkit import prompt -from unidecode import unidecode -from .common import multi_choice +try: + from unidecode import unidecode + + cli_installed = True +except ImportError: + cli_installed = False + +from .common import check_installed, multi_choice class Combiner(object): @@ -30,8 +33,9 @@ def separate(self, group): """ Ensure that the automated grouping is correct. - :param group: a list of ParsedSegment instances that are probably the same contributor - :return: a list of lists of ParsedSegments + :param group: a list of ParsedSegment instances that are probably the same + contributor + :returns: a list of lists of ParsedSegments """ all_names = sorted( list(set([str(x.name) for x in group])), key=lambda x: -len(x) @@ -92,8 +96,10 @@ def combine_person_names(self, names): """ Uses a list of HumanNames to determine the longest possible name for a person. - :return: a dict of family_name, given_names (includes middle names), and key (i.e. a sort/display name) + :returns: a dict of family_name, given_names (includes middle names), and key + (i.e. a sort/display name) """ + check_installed(cli_installed) def _filter_diacritics(name_list): filtered = [n for n in name_list if unidecode(n) != n] @@ -162,7 +168,7 @@ def run(self): Run the combiner over the whole parser list, including separating groups, combining names, searching APIs, and updating the affiliations dict. - :return: a list of contributors + :returns: a list of contributors """ combined = [] diff --git a/ckanext/attribution/commands/migration/common.py b/ckanext/attribution/commands/migration/common.py index 9c42e22..5abc4bd 100644 --- a/ckanext/attribution/commands/migration/common.py +++ b/ckanext/attribution/commands/migration/common.py @@ -14,7 +14,7 @@ def multi_choice(question, options, default=0): """ Provide the user with a list of options. - :return: index of the chosen option + :returns: index of the chosen option """ click.echo(question) for i, o in enumerate(options): @@ -25,7 +25,7 @@ def multi_choice(question, options, default=0): click.echo('') return answer - 1 except: - click.echo('That wasn\'t an option.', err=True) + click.echo("That wasn't an option.", err=True) return multi_choice(question, options, default) @@ -40,3 +40,15 @@ def multi_choice(question, options, default=0): initialism=re.compile(r'^[A-Z.]+$'), abbr=re.compile(r'([A-Z]|(?<=[^A-Za-z])[a-z])'), ) + + +def check_installed(is_cli_installed): + """ + Returns an error message if additional CLI packages are not installed. + + :param is_cli_installed: True if additional packages are installed + """ + if not is_cli_installed: + raise click.Exception( + 'Install additional requirements with: pip install ckanext-attribution[cli]' + ) diff --git a/ckanext/attribution/commands/migration/parser.py b/ckanext/attribution/commands/migration/parser.py index 0bf8756..17fc324 100644 --- a/ckanext/attribution/commands/migration/parser.py +++ b/ckanext/attribution/commands/migration/parser.py @@ -9,11 +9,17 @@ from textwrap import shorten import click -import spacy -from nameparser import HumanName -from prompt_toolkit import prompt -from .common import rgx, multi_choice +try: + import spacy + from nameparser import HumanName + from prompt_toolkit import prompt + + cli_installed = True +except ImportError: + cli_installed = False + +from .common import check_installed, multi_choice, rgx @dataclass @@ -30,6 +36,8 @@ class Parser(object): """ def __init__(self): + check_installed(cli_installed) + self.contributors = {'person': {}, 'org': {}, 'other': {}} self.affiliations = {} spacy_model = 'en_core_web_trf' @@ -46,7 +54,7 @@ def run(self, txt, pkg_id, contrib_type): :param txt: the chunk of text to process :param pkg_id: associated package :param contrib_type: author, contributor, or affiliation - :return: list of ParsedSegment instances extracted from the text + :returns: list of ParsedSegment instances extracted from the text """ if not self.validate(txt): return @@ -99,7 +107,7 @@ def validate(self, txt): """ Check the text can/should actually be parsed. - :return: True/False + :returns: True/False """ if txt is None: return False @@ -111,7 +119,7 @@ def validate(self, txt): return [] pc_proper_nouns = pos.get('PROPN', 0) / len(tokens) if pc_proper_nouns < 0.5: - click.echo('\nThis text doesn\'t look right:') + click.echo("\nThis text doesn't look right:") click.echo(txt) return not click.confirm('Skip it?') return True @@ -121,8 +129,8 @@ def split(self, txt): Uses multiple sub-methods to attempt to split the text into individual contributors. - :param txt: - :return: + :param txt: a string containing multiple contributor names + :returns: a list of names """ lines = [ln.strip() for ln in txt.split('\n')] segments = [ @@ -216,7 +224,7 @@ def extract_affiliations(self, txt): """ Uses regexes to find probable affiliations in parentheses. - :return: contributor name, list of affiliations + :returns: contributor name, list of affiliations """ has_affiliation = rgx.has_affiliation.match(txt) no_affiliation = rgx.no_affiliation.match(txt) diff --git a/ckanext/attribution/lib/helpers.py b/ckanext/attribution/lib/helpers.py index 1d3cd20..c76c7b3 100644 --- a/ckanext/attribution/lib/helpers.py +++ b/ckanext/attribution/lib/helpers.py @@ -7,15 +7,16 @@ import itertools import re -from ckan.plugins import toolkit, plugin_loaded -from ckanext.attribution.model.crud import PackageQuery, AgentQuery +from ckan.plugins import plugin_loaded, toolkit + +from ckanext.attribution.model.crud import AgentQuery, PackageQuery def can_edit(): """ Check editing permissions for updating agent directly. - :return: + :returns: True if user can edit agents, False if not """ try: permitted = toolkit.check_access('agent_update', {}, {}) @@ -29,12 +30,13 @@ def split_caps(string_input): def get_contributions(pkg_id): - ''' + """ Template access for the :func:`~ckanext.attribution.model.crud.PackageQuery.get_contributions` query method. - :param pkg_id: - :return: - ''' + + :param pkg_id: the ID of the package + :returns: list of activities associated with the package + """ return PackageQuery.get_contributions(pkg_id) diff --git a/ckanext/attribution/lib/orcid_api.py b/ckanext/attribution/lib/orcid_api.py index 8728cbe..7ba76c9 100644 --- a/ckanext/attribution/lib/orcid_api.py +++ b/ckanext/attribution/lib/orcid_api.py @@ -11,10 +11,6 @@ class OrcidApi(object): - """ - - """ - def __init__(self): self.key = toolkit.config.get('ckanext.attribution.orcid_key') self.secret = toolkit.config.get('ckanext.attribution.orcid_secret') @@ -22,18 +18,12 @@ def __init__(self): @cached_property def conn(self): - """ - - """ if self.key is None or self.secret is None: raise Exception(toolkit._('ORCID API credentials not supplied.')) return orcid.PublicAPI(self.key, self.secret, sandbox=self._debug) @cached_property def read_token(self): - """ - - """ if self.key is None or self.secret is None: raise Exception(toolkit._('ORCID API credentials not supplied.')) url = ( @@ -57,12 +47,6 @@ def read_token(self): return None def search(self, orcid_q=None, q=None, family_name=None, given_names=None): - ''' - - :param orcid_q: (Default value = None) - :param q: (Default value = None) - - ''' query = [] if orcid_q is not None and orcid_q != '': query.append('orcid:' + orcid_q) @@ -77,7 +61,7 @@ def search(self, orcid_q=None, q=None, family_name=None, given_names=None): records = [] loaded_ids = [] for r in search_response.get('result', []): - _id = r.get(u'orcid-identifier', {}).get(u'path', None) + _id = r.get('orcid-identifier', {}).get('path', None) if _id is not None and _id not in loaded_ids: try: orcid_record = self.as_agent_record(self.read(_id)) @@ -90,22 +74,10 @@ def search(self, orcid_q=None, q=None, family_name=None, given_names=None): return result def read(self, orcid_id): - ''' - - - :param orcid_id: - - ''' record = self.conn.read_record_public(orcid_id, 'record', self.read_token) return record def as_agent_record(self, orcid_record): - ''' - - - :param orcid_record: - - ''' names = orcid_record.get('person', {}).get('name', {}) return { 'family_name': names.get('family-name', {}).get('value', ''), diff --git a/ckanext/attribution/lib/ror_api.py b/ckanext/attribution/lib/ror_api.py index c3db6db..37375a7 100644 --- a/ckanext/attribution/lib/ror_api.py +++ b/ckanext/attribution/lib/ror_api.py @@ -4,20 +4,15 @@ # This file is part of ckanext-attribution # Created by the Natural History Museum in London, UK -import requests import json +import requests + class RorApi(object): url = 'https://api.ror.org' def read(self, ror_id): - ''' - - - :param ror_id: - - ''' response = requests.get('{0}/organizations/{1}'.format(self.url, ror_id)) if not response.ok: return None @@ -48,12 +43,6 @@ def search(self, q): } def as_agent_record(self, ror_record): - ''' - - - :param ror_record: - - ''' if ror_record is None: return {} address = ror_record.get('addresses') diff --git a/ckanext/attribution/logic/actions/create.py b/ckanext/attribution/logic/actions/create.py index 514a7d4..728d58f 100644 --- a/ckanext/attribution/logic/actions/create.py +++ b/ckanext/attribution/logic/actions/create.py @@ -5,19 +5,20 @@ # Created by the Natural History Museum in London, UK from ckan.plugins import toolkit +from ckantools.decorators import action, basic_action + from ckanext.attribution.logic.actions.helpers import ( - parse_contributors, get_author_string, + parse_contributors, ) +from ckanext.attribution.logic.actions.meta import help, schema from ckanext.attribution.model.crud import ( + AgentAffiliationQuery, AgentContributionActivityQuery, AgentQuery, ContributionActivityQuery, PackageContributionActivityQuery, - AgentAffiliationQuery, ) -from ckantools.decorators import action, basic_action -from ckanext.attribution.logic.actions.meta import help, schema @action(schema.agent_affiliation_create, help.agent_affiliation_create) diff --git a/ckanext/attribution/logic/actions/delete.py b/ckanext/attribution/logic/actions/delete.py index 6f7e058..1ac6c1a 100644 --- a/ckanext/attribution/logic/actions/delete.py +++ b/ckanext/attribution/logic/actions/delete.py @@ -4,6 +4,8 @@ # This file is part of ckanext-attribution # Created by the Natural History Museum in London, UK +from ckantools.decorators import action + from ckanext.attribution.logic.actions.meta import help, schema from ckanext.attribution.model.crud import ( AgentAffiliationQuery, @@ -12,7 +14,6 @@ ContributionActivityQuery, PackageContributionActivityQuery, ) -from ckantools.decorators import action @action(schema.agent_affiliation_delete, help.agent_affiliation_delete) diff --git a/ckanext/attribution/logic/actions/extra.py b/ckanext/attribution/logic/actions/extra.py index b179854..83cecd3 100644 --- a/ckanext/attribution/logic/actions/extra.py +++ b/ckanext/attribution/logic/actions/extra.py @@ -3,14 +3,16 @@ # # This file is part of ckanext-attribution # Created by the Natural History Museum in London, UK + import re from ckan.plugins import toolkit +from ckantools.decorators import action + from ckanext.attribution.lib.orcid_api import OrcidApi from ckanext.attribution.lib.ror_api import RorApi +from ckanext.attribution.logic.actions.meta import help, schema from ckanext.attribution.model.crud import AgentQuery -from ckantools.decorators import action -from ckanext.attribution.logic.actions.meta import schema, help @action( @@ -109,7 +111,7 @@ def agent_external_search(q, sources): orcid_remaining = 0 orcidapi = OrcidApi() orcid_search = orcidapi.search(q=q) - n = orcid_search.get(u'total', 0) + n = orcid_search.get('total', 0) orcid_records = orcid_search.get('records') orcid_records = [ r @@ -125,7 +127,7 @@ def agent_external_search(q, sources): ror_remaining = 0 rorapi = RorApi() ror_search = rorapi.search(q=q) - n = ror_search.get(u'total', 0) + n = ror_search.get('total', 0) ror_records = ror_search.get('records') ror_records = [ r diff --git a/ckanext/attribution/logic/actions/helpers.py b/ckanext/attribution/logic/actions/helpers.py index c8c1039..c72a93f 100644 --- a/ckanext/attribution/logic/actions/helpers.py +++ b/ckanext/attribution/logic/actions/helpers.py @@ -2,13 +2,13 @@ import json from ckan.plugins import toolkit + from ckanext.attribution.model.crud import ( - AgentQuery, + AgentAffiliationQuery, AgentContributionActivityQuery, + AgentQuery, ContributionActivityQuery, - AgentAffiliationQuery, PackageQuery, - PackageContributionActivityQuery, ) diff --git a/ckanext/attribution/logic/actions/meta/help.py b/ckanext/attribution/logic/actions/meta/help.py index 881685e..0136149 100644 --- a/ckanext/attribution/logic/actions/meta/help.py +++ b/ckanext/attribution/logic/actions/meta/help.py @@ -6,7 +6,7 @@ # CREATE =========================================================================================== -agent_affiliation_create = ''' +agent_affiliation_create = """ Create an :class:`~ckanext.attribution.model.agent_affiliation.AgentAffiliation` link record between two :class:`~ckanext.attribution.model.agent.Agent` records, e.g. to show institutional affiliation for an author. @@ -25,9 +25,9 @@ :type end_date: datetime.date, optional :returns: New agent affiliation record. :rtype: dict -''' +""" -agent_create = ''' +agent_create = """ Action for creating an :class:`~ckanext.attribution.model.agent.Agent` record. Different fields are required by different agent types. @@ -47,9 +47,9 @@ :type name: str, optional :returns: New agent record. :rtype: dict -''' +""" -contribution_activity_create = ''' +contribution_activity_create = """ Creates a :class:`~ckanext.attribution.model.contribution_activity.ContributionActivity` record, linked to a package and an agent via package_contribution_activity and agent_contribution_activity records (respectively). These link records are also created as part of this action, as the activity @@ -69,29 +69,29 @@ :type time: datetime.datetime, optional :returns: New contribution activity record. :rtype: dict -''' +""" # DELETE =========================================================================================== -agent_affiliation_delete = ''' +agent_affiliation_delete = """ Delete an :class:`~ckanext.attribution.model.agent_affiliation.AgentAffiliation` record by ID. :param id: ID of the affiliation record :type id: str :returns: The affiliation record. :rtype: dict -''' +""" -agent_delete = ''' +agent_delete = """ Delete an :class:`~ckanext.attribution.model.agent.Agent` record by ID. :param id: ID of the agent record :type id: str :returns: The agent record. :rtype: dict -''' +""" -agent_contribution_activity_delete = ''' +agent_contribution_activity_delete = """ Delete an :class:`~ckanext.attribution.model.agent_contribution_activity.AgentContributionActivity` record by ID. @@ -99,9 +99,9 @@ :type id: str :returns: The agent contribution activity record. :rtype: dict -''' +""" -contribution_activity_delete = ''' +contribution_activity_delete = """ Delete a :class:`~ckanext.attribution.model.contribution_activity.ContributionActivity` record by ID. @@ -109,9 +109,9 @@ :type id: str :returns: The contribution activity record. :rtype: dict -''' +""" -package_contribution_activity_delete = ''' +package_contribution_activity_delete = """ Delete a :class:`~ckanext.attribution.model.package_contribution_activity.PackageContributionActivity` record by ID. @@ -120,22 +120,22 @@ :type id: str :returns: The package contribution activity record. :rtype: dict -''' +""" # EXTRA ============================================================================================ -attribution_controlled_lists = ''' +attribution_controlled_lists = """ Return one or more lists or dicts of defined values, e.g. agent types or contribution activity types. Details dicts can include various pieces of arbitrary information (e.g. names, translations, or icon definitions for templates) as long as the initial structure is retained. :param lists: names of the lists to be returned :type lists: list, optional -:return: dict of all requested lists (or all lists if unspecified) +:returns: dict of all requested lists (or all lists if unspecified) :rtype: dict -''' +""" -agent_external_search = ''' +agent_external_search = """ Search external sources for agent data. Ignores records that already exist in the database. :param q: searches all fields (names, ids, etc) @@ -144,9 +144,9 @@ :type sources: list, optional :returns: A list of potential matches. :rtype: list -''' +""" -agent_external_read = ''' +agent_external_read = """ Read data from an external source like ORCID or ROR. :param agent_id: ID of the record to read @@ -160,39 +160,39 @@ :type diff: bool, optional :returns: The details pulled from the external source, formatted as a record dict :rtype: dict -''' +""" -validate_external_id = ''' +validate_external_id = """ Validate/format an external ID. :param external_id: ID from external service :type external_id: str :param external_id_scheme: external scheme, e.g. orcid :type external_id_scheme: str -:return: -''' +:returns: dict containing the ID if valid and any errors +""" # SHOW ============================================================================================= -agent_affiliation_show = ''' +agent_affiliation_show = """ Retrieve an :class:`~ckanext.attribution.model.agent_affiliation.AgentAffiliation` record by ID. :param id: ID of the affiliation record :type id: str :returns: The affiliation record. :rtype: dict -''' +""" -agent_show = ''' +agent_show = """ Retrieve an :class:`~ckanext.attribution.model.agent.Agent` record by ID. :param id: ID of the agent record :type id: str :returns: The agent record. :rtype: dict -''' +""" -agent_list = ''' +agent_list = """ Search for :class:`~ckanext.attribution.model.agent.Agent` records. :param q: name or external id (ORCID/ROR ID) of the agent record @@ -201,9 +201,9 @@ :type mode: str, optional :returns: A list of potential matches. :rtype: list -''' +""" -agent_contribution_activity_show = ''' +agent_contribution_activity_show = """ Retrieve an :class:`~ckanext.attribution.model.agent_contribution_activity.AgentContributionActivity` record by ID. @@ -212,9 +212,9 @@ :type id: str :returns: The agent contribution activity record. :rtype: dict -''' +""" -contribution_activity_show = ''' +contribution_activity_show = """ Retrieve a :class:`~ckanext.attribution.model.contribution_activity.ContributionActivity` record by ID. @@ -222,9 +222,9 @@ :type id: str :returns: The contribution activity record. :rtype: dict -''' +""" -package_contribution_activity_show = ''' +package_contribution_activity_show = """ Retrieve a :class:`~ckanext.attribution.model.package_contribution_activity.PackageContributionActivity` record by ID. @@ -233,9 +233,9 @@ :type id: str :returns: The package contribution activity record. :rtype: dict -''' +""" -package_contributions_show = ''' +package_contributions_show = """ Show associated agents and their contributions for a given package. Agents are returned in citation then agent id order. @@ -247,10 +247,10 @@ :type offset: int :returns: The package contribution activity record. :rtype: dict -''' +""" -agent_affiliations = ''' +agent_affiliations = """ Show affiliated agents, either all or for a given package. Including a package ID will still return 'global' affiliations, e.g. those with no specific package associated. @@ -260,11 +260,11 @@ :type package_id: str, optional :returns: The package contribution activity record. :rtype: dict -''' +""" # UPDATE =========================================================================================== -agent_affiliation_update = ''' +agent_affiliation_update = """ Update an :class:`~ckanext.attribution.model.agent_affiliation.AgentAffiliation` link record. :param id: ID of the record to update @@ -281,13 +281,13 @@ :type start_date: datetime.date, optional :param end_date: when the affiliation ended (e.g. when a researcher left an institution) :type end_date: datetime.date, optional -:param context: -:param data_dict: +:param context: request context +:param data_dict: request data :returns: The updated agent affiliation record. :rtype: dict -''' +""" -agent_update = ''' +agent_update = """ Action for updating an :class:`~ckanext.attribution.model.agent.Agent` record. Different fields are required by different agent types. @@ -307,13 +307,13 @@ :type user_id: str, optional :param name: name of an organisation [org only] :type name: str, optional -:param context: -:param data_dict: +:param context: request context +:param data_dict: request data :returns: The updated agent record. :rtype: dict -''' +""" -agent_external_update = ''' +agent_external_update = """ Action for updating an :class:`~ckanext.attribution.model.agent.Agent` record by pulling information from an external source like ORCID or ROR. @@ -321,9 +321,9 @@ :type id: str :returns: The updated agent record. :rtype: dict -''' +""" -contribution_activity_update = ''' +contribution_activity_update = """ Updates a :class:`~ckanext.attribution.model.contribution_activity.ContributionActivity` record, linked to a package and an agent via package_contribution_activity and agent_contribution_activity records (respectively). These link records are also updated as part of this action, as the activity @@ -339,8 +339,8 @@ :type level: str, optional :param time: time activity took place :type time: datetime.datetime, optional -:param context: -:param data_dict: +:param context: request context +:param data_dict: request data :returns: New contribution activity record. :rtype: dict -''' +""" diff --git a/ckanext/attribution/logic/actions/meta/schema.py b/ckanext/attribution/logic/actions/meta/schema.py index bd1058e..9f9022b 100644 --- a/ckanext/attribution/logic/actions/meta/schema.py +++ b/ckanext/attribution/logic/actions/meta/schema.py @@ -4,9 +4,8 @@ # This file is part of ckanext-attribution # Created by the Natural History Museum in London, UK -from ckantools.validators import list_of_strings - from ckan.plugins import toolkit +from ckantools.validators import list_of_strings # grab all the validator functions upfront ignore_missing = toolkit.get_validator('ignore_missing') diff --git a/ckanext/attribution/logic/actions/show.py b/ckanext/attribution/logic/actions/show.py index 96b397d..a575b97 100644 --- a/ckanext/attribution/logic/actions/show.py +++ b/ckanext/attribution/logic/actions/show.py @@ -6,6 +6,11 @@ import itertools +from ckan.plugins import toolkit +from ckantools.decorators import action +from fuzzywuzzy import fuzz +from sqlalchemy import or_ + from ckanext.attribution.logic.actions.meta import help, schema from ckanext.attribution.model.crud import ( AgentAffiliationQuery, @@ -15,11 +20,6 @@ PackageContributionActivityQuery, PackageQuery, ) -from ckantools.decorators import action -from fuzzywuzzy import fuzz -from sqlalchemy import or_ - -from ckan.plugins import toolkit @action(schema.agent_affiliation_show, help.agent_affiliation_show, get=True) diff --git a/ckanext/attribution/logic/actions/update.py b/ckanext/attribution/logic/actions/update.py index 2cb5a56..4691af1 100644 --- a/ckanext/attribution/logic/actions/update.py +++ b/ckanext/attribution/logic/actions/update.py @@ -5,18 +5,19 @@ # Created by the Natural History Museum in London, UK from ckan.plugins import toolkit +from ckantools.decorators import action, basic_action + from ckanext.attribution.logic.actions.helpers import ( - parse_contributors, get_author_string, + parse_contributors, ) +from ckanext.attribution.logic.actions.meta import help, schema from ckanext.attribution.model.crud import ( - AgentQuery, - ContributionActivityQuery, AgentAffiliationQuery, AgentContributionActivityQuery, + AgentQuery, + ContributionActivityQuery, ) -from ckantools.decorators import action, basic_action -from ckanext.attribution.logic.actions.meta import help, schema @action(schema.agent_affiliation_update, help.agent_affiliation_update) diff --git a/ckanext/attribution/logic/auth/delete.py b/ckanext/attribution/logic/auth/delete.py index 7b067f0..78f6adb 100644 --- a/ckanext/attribution/logic/auth/delete.py +++ b/ckanext/attribution/logic/auth/delete.py @@ -4,11 +4,10 @@ # This file is part of ckanext-attribution # Created by the Natural History Museum in London, UK +from ckan.authz import is_sysadmin from ckantools.decorators import auth from ckantools.vars import auth_valid -from ckan.authz import is_sysadmin - @auth() def agent_affiliation_delete(context, data_dict): diff --git a/ckanext/attribution/logic/auth/show.py b/ckanext/attribution/logic/auth/show.py index 0564e2d..604327b 100644 --- a/ckanext/attribution/logic/auth/show.py +++ b/ckanext/attribution/logic/auth/show.py @@ -6,7 +6,6 @@ from ckantools.decorators import auth from ckantools.vars import auth_valid -from ckan.plugins import toolkit @auth(anon=True) diff --git a/ckanext/attribution/logic/auth/update.py b/ckanext/attribution/logic/auth/update.py index d50c3f1..c4e1960 100644 --- a/ckanext/attribution/logic/auth/update.py +++ b/ckanext/attribution/logic/auth/update.py @@ -4,11 +4,10 @@ # This file is part of ckanext-attribution # Created by the Natural History Museum in London, UK +from ckan.authz import is_sysadmin from ckantools.decorators import auth from ckantools.vars import auth_valid -from ckan.authz import is_sysadmin - @auth() def agent_affiliation_update(context, data_dict): diff --git a/ckanext/attribution/model/agent.py b/ckanext/attribution/model/agent.py index 621d54d..cacb7e8 100644 --- a/ckanext/attribution/model/agent.py +++ b/ckanext/attribution/model/agent.py @@ -55,9 +55,9 @@ def display_name(self): @property def standardised_name(self): """ - Family name first for persons. + Name in a standardised format. - :return: + :returns: full name (family name first) for person names, just name for other """ if self.agent_type == 'person': return ', '.join([self.family_name, self.given_names]) diff --git a/ckanext/attribution/model/agent_affiliation.py b/ckanext/attribution/model/agent_affiliation.py index a0f1a29..10c9e15 100644 --- a/ckanext/attribution/model/agent_affiliation.py +++ b/ckanext/attribution/model/agent_affiliation.py @@ -6,9 +6,10 @@ from ckan.model import DomainObject, meta from ckan.model.types import make_uuid -from ckanext.attribution.model.agent import agent_table from sqlalchemy import Column, Date, ForeignKey, Table, UnicodeText +from ckanext.attribution.model.agent import agent_table + agent_affiliation_table = Table( 'agent_affiliation', meta.metadata, @@ -54,8 +55,5 @@ def other_agent(self, agent_id): def check_for_table(): - """ - - """ if agent_table.exists(): agent_affiliation_table.create(checkfirst=True) diff --git a/ckanext/attribution/model/agent_contribution_activity.py b/ckanext/attribution/model/agent_contribution_activity.py index 5979c16..fa656b9 100644 --- a/ckanext/attribution/model/agent_contribution_activity.py +++ b/ckanext/attribution/model/agent_contribution_activity.py @@ -6,9 +6,10 @@ from ckan.model import DomainObject, meta from ckan.model.types import make_uuid +from sqlalchemy import Column, ForeignKey, Table, UnicodeText + from ckanext.attribution.model.agent import agent_table from ckanext.attribution.model.contribution_activity import contribution_activity_table -from sqlalchemy import Column, ForeignKey, Table, UnicodeText agent_contribution_activity_table = Table( 'agent_contribution_activity', @@ -39,8 +40,5 @@ class AgentContributionActivity(DomainObject): def check_for_table(): - """ - - """ if agent_table.exists() and contribution_activity_table.exists(): agent_contribution_activity_table.create(checkfirst=True) diff --git a/ckanext/attribution/model/contribution_activity.py b/ckanext/attribution/model/contribution_activity.py index 322c0c4..4805103 100644 --- a/ckanext/attribution/model/contribution_activity.py +++ b/ckanext/attribution/model/contribution_activity.py @@ -6,7 +6,7 @@ from ckan.model import DomainObject, meta from ckan.model.types import make_uuid -from sqlalchemy import Column, DateTime, Table, UnicodeText, Integer +from sqlalchemy import Column, DateTime, Integer, Table, UnicodeText # this table stores contribution activities contribution_activity_table = Table( @@ -30,7 +30,4 @@ class ContributionActivity(DomainObject): def check_for_table(): - """ - - """ contribution_activity_table.create(checkfirst=True) diff --git a/ckanext/attribution/model/crud/__init__.py b/ckanext/attribution/model/crud/__init__.py index d9287bc..2ad522f 100644 --- a/ckanext/attribution/model/crud/__init__.py +++ b/ckanext/attribution/model/crud/__init__.py @@ -2,5 +2,5 @@ from .agent_affiliation import AgentAffiliationQuery from .agent_contribution_activity import AgentContributionActivityQuery from .contribution_activity import ContributionActivityQuery -from .package_contribution_activity import PackageContributionActivityQuery from .package import PackageQuery +from .package_contribution_activity import PackageContributionActivityQuery diff --git a/ckanext/attribution/model/crud/_base.py b/ckanext/attribution/model/crud/_base.py index 158c3d6..f1db0c9 100644 --- a/ckanext/attribution/model/crud/_base.py +++ b/ckanext/attribution/model/crud/_base.py @@ -5,9 +5,10 @@ # Created by the Natural History Museum in London, UK from abc import ABCMeta + from ckan.model import DomainObject, Session -from sqlalchemy import Table from ckan.plugins import toolkit +from sqlalchemy import Table class BaseQuery(object): @@ -26,12 +27,6 @@ class BaseQuery(object): @classmethod def _columns(cls, **kwargs): - ''' - - - :param kwargs: - - ''' return {c.name: kwargs.get(c.name) for c in cls.t.c if c.name in kwargs} @classmethod @@ -97,7 +92,7 @@ def exists(cls, item_id): Check if a record with the given ID exists. :param item_id: the ID of the potential record - :return: bool + :returns: bool """ return Session.query(cls.m).get(item_id) is not None @@ -119,13 +114,6 @@ def all(cls): @classmethod def update(cls, item_id, **kwargs): - ''' - - - :param item_id: - :param kwargs: - - ''' try: del kwargs['id'] except KeyError: @@ -139,12 +127,6 @@ def update(cls, item_id, **kwargs): @classmethod def delete(cls, item_id): - ''' - - - :param item_id: - - ''' to_delete = Session.query(cls.m).get(item_id) if to_delete is not None: Session.delete(to_delete) diff --git a/ckanext/attribution/model/crud/agent.py b/ckanext/attribution/model/crud/agent.py index 26d57c8..dcf8b5d 100644 --- a/ckanext/attribution/model/crud/agent.py +++ b/ckanext/attribution/model/crud/agent.py @@ -6,10 +6,11 @@ from ckan.model import Session from ckan.plugins import toolkit +from requests import HTTPError + from ckanext.attribution.lib.orcid_api import OrcidApi from ckanext.attribution.lib.ror_api import RorApi from ckanext.attribution.model.agent import Agent, agent_table -from requests import HTTPError from ._base import BaseQuery @@ -18,23 +19,23 @@ class AgentQuery(BaseQuery): """ CRUD methods for :class:`~ckanext.attribution.model.agent.Agent`. - Fields - ====== + Fields ====== :param agent_type: broad type of agent; usually 'person' or 'org' :type agent_type: str :param external_id: the agent's ID from an external service like ORCID or ROR :type external_id: str, optional - :param external_id_scheme: the name of the scheme for the external ID, e.g. 'orcid' or 'ror' + :param external_id_scheme: the name of the scheme for the external ID, e.g. 'orcid' + or 'ror' :type external_id_scheme: str, optional :param family_name: family name of an person [person only, required] :type family_name: str, optional :param given_names: given name(s) or initials of an person [person only, required] :type given_names: str, optional - :param given_names_first: whether given names should be displayed before the family name - (default True) [person only, optional] + :param given_names_first: whether given names should be displayed before the family + name (default True) [person only, optional] :type given_names_first: bool, optional - :param user_id: the ID for a registered user of this CKAN instance associated with this agent - [person only, optional] + :param user_id: the ID for a registered user of this CKAN instance associated with + this agent [person only, optional] :type user_id: str, optional :param name: name of an organisation [org only, required] :type name: str, optional @@ -124,8 +125,9 @@ def _read_from_orcid_api(cls, record, api=None): :param record: the existing record :type record: Agent - :param api: an API instance already in use (useful if performing this action over many - agent records, to avoid instantiating many API connections) (Default value = None) + :param api: an API instance already in use (useful if performing this action + over many agent records, to avoid instantiating many API connections) + (Default value = None) :type api: OrcidApi :returns: the updated agent record :rtype: Agent diff --git a/ckanext/attribution/model/crud/agent_affiliation.py b/ckanext/attribution/model/crud/agent_affiliation.py index 11270f1..36104d8 100644 --- a/ckanext/attribution/model/crud/agent_affiliation.py +++ b/ckanext/attribution/model/crud/agent_affiliation.py @@ -4,20 +4,17 @@ # This file is part of ckanext-attribution # Created by the Natural History Museum in London, UK +from sqlalchemy import and_, or_ + from ckanext.attribution.model.agent_affiliation import ( AgentAffiliation, agent_affiliation_table, ) from ._base import BaseQuery -from sqlalchemy import or_, and_ class AgentAffiliationQuery(BaseQuery): - """ - - """ - # model and table (subclasses should override) m = AgentAffiliation t = agent_affiliation_table diff --git a/ckanext/attribution/model/crud/agent_contribution_activity.py b/ckanext/attribution/model/crud/agent_contribution_activity.py index 5818308..d54d7f5 100644 --- a/ckanext/attribution/model/crud/agent_contribution_activity.py +++ b/ckanext/attribution/model/crud/agent_contribution_activity.py @@ -13,10 +13,6 @@ class AgentContributionActivityQuery(BaseQuery): - """ - - """ - # model and table (subclasses should override) m = AgentContributionActivity t = agent_contribution_activity_table diff --git a/ckanext/attribution/model/crud/contribution_activity.py b/ckanext/attribution/model/crud/contribution_activity.py index 925843f..8ea7729 100644 --- a/ckanext/attribution/model/crud/contribution_activity.py +++ b/ckanext/attribution/model/crud/contribution_activity.py @@ -13,10 +13,6 @@ class ContributionActivityQuery(BaseQuery): - """ - - """ - # model and table (subclasses should override) m = ContributionActivity t = contribution_activity_table diff --git a/ckanext/attribution/model/crud/package.py b/ckanext/attribution/model/crud/package.py index 64e0844..51f0508 100644 --- a/ckanext/attribution/model/crud/package.py +++ b/ckanext/attribution/model/crud/package.py @@ -4,9 +4,8 @@ # This file is part of ckanext-attribution # Created by the Natural History Museum in London, UK -import itertools -from ckan.model import Package, package_table, Session +from ckan.model import Package, Session, package_table from ckan.plugins import toolkit from ._base import BaseQuery @@ -14,10 +13,6 @@ class PackageQuery(BaseQuery): - """ - - """ - m = Package t = package_table diff --git a/ckanext/attribution/model/crud/package_contribution_activity.py b/ckanext/attribution/model/crud/package_contribution_activity.py index e7e7471..dbbc07a 100644 --- a/ckanext/attribution/model/crud/package_contribution_activity.py +++ b/ckanext/attribution/model/crud/package_contribution_activity.py @@ -4,19 +4,17 @@ # This file is part of ckanext-attribution # Created by the Natural History Museum in London, UK +from ckan.model import Session + from ckanext.attribution.model.package_contribution_activity import ( PackageContributionActivity, package_contribution_activity_table, ) -from ckan.model import Session + from ._base import BaseQuery class PackageContributionActivityQuery(BaseQuery): - """ - - """ - # model and table (subclasses should override) m = PackageContributionActivity t = package_contribution_activity_table diff --git a/ckanext/attribution/model/package_contribution_activity.py b/ckanext/attribution/model/package_contribution_activity.py index 96820f7..a86e82f 100644 --- a/ckanext/attribution/model/package_contribution_activity.py +++ b/ckanext/attribution/model/package_contribution_activity.py @@ -6,9 +6,10 @@ from ckan.model import DomainObject, meta, package_table from ckan.model.types import make_uuid -from ckanext.attribution.model.contribution_activity import contribution_activity_table from sqlalchemy import Column, ForeignKey, Table, UnicodeText +from ckanext.attribution.model.contribution_activity import contribution_activity_table + package_contribution_activity_table = Table( 'package_contribution_activity', meta.metadata, @@ -38,8 +39,5 @@ class PackageContributionActivity(DomainObject): def check_for_table(): - """ - - """ if package_table.exists() and contribution_activity_table.exists(): package_contribution_activity_table.create(checkfirst=True) diff --git a/ckanext/attribution/model/relationships.py b/ckanext/attribution/model/relationships.py index 9697051..38e49b7 100644 --- a/ckanext/attribution/model/relationships.py +++ b/ckanext/attribution/model/relationships.py @@ -3,7 +3,6 @@ # # This file is part of ckanext-attribution # Created by the Natural History Museum in London, UK - """ These relationships are defined separately to the object/table declarations to allow them to reference each other without circular imports. @@ -11,7 +10,7 @@ from ckan.model import Package, User, meta from sqlalchemy import or_ -from sqlalchemy.orm import backref, relationship, attributes +from sqlalchemy.orm import attributes, backref, relationship from .agent import Agent, agent_table from .agent_affiliation import AgentAffiliation, agent_affiliation_table diff --git a/ckanext/attribution/plugin.py b/ckanext/attribution/plugin.py index db83fe3..08eb630 100644 --- a/ckanext/attribution/plugin.py +++ b/ckanext/attribution/plugin.py @@ -5,6 +5,10 @@ # Created by the Natural History Museum in London, UK from ckan.plugins import SingletonPlugin, implements, interfaces, toolkit +from ckantools.loaders import create_actions, create_auth + +from ckanext.attribution import routes +from ckanext.attribution.commands import cli from ckanext.attribution.lib import helpers from ckanext.attribution.model import ( agent, @@ -14,9 +18,6 @@ package_contribution_activity, relationships, ) -from ckanext.attribution.commands import cli -from ckanext.attribution import routes -from ckantools.loaders import create_actions, create_auth try: from ckanext.doi.interfaces import IDoi @@ -47,17 +48,17 @@ class AttributionPlugin(SingletonPlugin): def get_actions(self): from ckanext.attribution.logic.actions import ( create, - show, - update, delete, extra, + show, + update, ) return create_actions(create, show, update, delete, extra) # IAuthFunctions def get_auth_functions(self): - from ckanext.attribution.logic.auth import create, show, update, delete, extra + from ckanext.attribution.logic.auth import create, delete, extra, show, update return create_auth(create, show, update, delete, extra) diff --git a/ckanext/attribution/routes/user.py b/ckanext/attribution/routes/user.py index 9870d81..7608f6a 100644 --- a/ckanext/attribution/routes/user.py +++ b/ckanext/attribution/routes/user.py @@ -6,6 +6,7 @@ from ckan.plugins import toolkit from flask import Blueprint + from ckanext.attribution.lib.helpers import user_contributions blueprint = Blueprint(name='attribution_user', import_name=__name__) @@ -17,7 +18,7 @@ def datasets(username): Render a list of datasets that this user has contributed to. :param username: The username - :return: str + :returns: str """ try: toolkit.check_access('user_show', {}, {'id': username}) diff --git a/ckanext/attribution/theme/assets/scripts/apps/package-edit/src/components/fields/Autocomplete.vue b/ckanext/attribution/theme/assets/scripts/apps/package-edit/src/components/fields/Autocomplete.vue index d4d5822..e44ddbd 100644 --- a/ckanext/attribution/theme/assets/scripts/apps/package-edit/src/components/fields/Autocomplete.vue +++ b/ckanext/attribution/theme/assets/scripts/apps/package-edit/src/components/fields/Autocomplete.vue @@ -116,10 +116,10 @@ export default { return this.failed ? 'fa-times' : this.loading - ? 'fa-spinner fa-spin' - : this.typing - ? 'fa-xs fa-ellipsis-h' - : 'fa-check'; + ? 'fa-spinner fa-spin' + : this.typing + ? 'fa-xs fa-ellipsis-h' + : 'fa-check'; }, }, methods: { diff --git a/ckanext/attribution/theme/assets/scripts/apps/package-edit/src/components/fields/ValidatedField.vue b/ckanext/attribution/theme/assets/scripts/apps/package-edit/src/components/fields/ValidatedField.vue index e229161..12e4732 100644 --- a/ckanext/attribution/theme/assets/scripts/apps/package-edit/src/components/fields/ValidatedField.vue +++ b/ckanext/attribution/theme/assets/scripts/apps/package-edit/src/components/fields/ValidatedField.vue @@ -48,10 +48,10 @@ export default { return this.failed ? 'fa-times' : this.loading - ? 'fa-spinner fa-spin' - : this.typing - ? 'fa-xs fa-ellipsis-h' - : 'fa-check'; + ? 'fa-spinner fa-spin' + : this.typing + ? 'fa-xs fa-ellipsis-h' + : 'fa-check'; }, }, methods: { diff --git a/docker-compose.yml b/docker-compose.yml index 91ac279..89b6e2e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,5 +1,3 @@ -version: "3" - services: ckan: build: diff --git a/docs/_scripts/gen_api_pages.py b/docs/_scripts/gen_api_pages.py index 8efa6b3..a09f789 100644 --- a/docs/_scripts/gen_api_pages.py +++ b/docs/_scripts/gen_api_pages.py @@ -1,6 +1,5 @@ # !/usr/bin/env python # encoding: utf-8 - """ Generate the code reference pages and navigation. diff --git a/pyproject.toml b/pyproject.toml index ec14643..80aa2d4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,15 +19,11 @@ classifiers = [ "Programming Language :: Python :: 3.8" ] dependencies = [ - "orcid", + "orcid~=1.0", "sqlalchemy", "requests", "fuzzywuzzy[speedup]", - "spacy[transformers]", - "unidecode", - "nameparser", - "prompt_toolkit", - "ckantools>=0.3.0" + "ckantools>=0.4.1" ] [project.optional-dependencies] @@ -37,6 +33,12 @@ test = [ "pytest-cov>=2.7.1", "coveralls" ] +cli = [ + "spacy[transformers]~=3.7.0", + "unidecode~=1.0", + "nameparser~=1.0", + "prompt_toolkit~=3.0" +] [project.urls] repository = "https://github.com/NaturalHistoryMuseum/ckanext-attribution" @@ -73,9 +75,8 @@ version_files = [ "CITATION.cff:^version" ] -[tool.black] -line-length = 88 -skip_string_normalization = true +[tool.ruff.format] +quote-style = "single" [tool.pylint] max-line-length = 88 diff --git a/tests/test_helpers.py b/tests/test_helpers.py index 352ba36..874b4df 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -1,6 +1,7 @@ import pytest -from ckan.tests import factories from ckan.common import g +from ckan.tests import factories + from ckanext.attribution.lib import helpers