diff --git a/.github/workflows/bot.yaml b/.github/workflows/bot.yaml index dd5a930..eafab97 100644 --- a/.github/workflows/bot.yaml +++ b/.github/workflows/bot.yaml @@ -30,15 +30,6 @@ jobs: with: python-version: '3.x' - - name: Check for Required Environment Variables - env: - CALTECHDATA_TOKEN: ${{ secrets.CALTECHDATA_TOKEN }} - run: | - if [ -z "$CALTECHDATA_TOKEN" ]; then - echo "Error: CALTECHDATA_TOKEN environment variable is not set" - exit 1 - fi - - name: Install dependencies run: | python -m pip install --upgrade pip diff --git a/.github/workflows/update_setupcfg.yaml b/.github/workflows/update_setupcfg.yaml new file mode 100644 index 0000000..defc510 --- /dev/null +++ b/.github/workflows/update_setupcfg.yaml @@ -0,0 +1,50 @@ +name: Sync Codemeta with Setup + +on: + push: + paths: + - codemeta.json + +jobs: + sync-codemeta: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.x' + + - name: Install jq for JSON parsing + run: sudo apt-get install -y jq + + - name: Parse and update setup.cfg + run: | + # Extract values from codemeta.json + NAME=$(jq -r '.name' codemeta.json) + VERSION=$(jq -r '.version' codemeta.json) + AUTHORS=$(jq -r '[.author[] | .givenName + " " + .familyName] | join(", ")' codemeta.json) + AUTHOR_EMAILS=$(jq -r '[.author[] | .email // empty] | join(", ")' codemeta.json) + DESCRIPTION=$(jq -r '.description' codemeta.json) + URL=$(jq -r '.codeRepository // .url' codemeta.json) + + # Update setup.cfg fields + sed -i "s/^name = .*/name = $NAME/" setup.cfg + sed -i "s/^version = .*/version = $VERSION/" setup.cfg + sed -i "s/^author = .*/author = $AUTHORS/" setup.cfg + sed -i "s/^author_email = .*/author_email = $AUTHOR_EMAILS/" setup.cfg + sed -i "s/^description = .*/description = $DESCRIPTION/" setup.cfg + sed -i "s|^url = .*|url = $URL|" setup.cfg + + - name: Commit changes + run: | + if ! git diff --quiet; then + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add setup.cfg + git commit -m "Sync setup.cfg with codemeta.json changes" + git push + fi diff --git a/CITATION.cff b/CITATION.cff index 755abe9..d341238 100755 --- a/CITATION.cff +++ b/CITATION.cff @@ -22,4 +22,4 @@ keywords: - metadata - software - InvenioRDM - +date-released: 2025-01-07 diff --git a/caltechdata_api/cli.py b/caltechdata_api/cli.py index 21afd7a..b0e9c00 100644 --- a/caltechdata_api/cli.py +++ b/caltechdata_api/cli.py @@ -99,6 +99,16 @@ def get_or_set_token(production=True): print("Tokens do not match. Please try again.") +# Delete the saved token file. +def delete_saved_token(): + token_file = os.path.join(caltechdata_directory, "token.txt") + if os.path.exists(token_file): + os.remove(token_file) + print("Token deleted successfully.") + else: + print("No token found to delete.") + + def welcome_message(): print("Welcome to CaltechDATA CLI") @@ -186,6 +196,61 @@ def get_funding_details(): } +# Add profile handling functions +def save_profile(): + profile_file = os.path.join(caltechdata_directory, "profile.json") + + # Get ORCID + while True: + orcid = get_user_input("Enter your ORCID identifier: ") + orcid = normalize_orcid(orcid) + family_name, given_name = get_names(orcid) + if family_name is not None and given_name is not None: + break + retry = input("Do you want to try again? (y/n): ") + if retry.lower() != "y": + return None + + # Get funding details + funding_references = [] + num_funding_entries = get_funding_entries() + for _ in range(num_funding_entries): + funding_references.append(get_funding_details()) + + profile_data = { + "orcid": orcid, + "family_name": family_name, + "given_name": given_name, + "funding_references": funding_references, + } + + with open(profile_file, "w") as f: + json.dump(profile_data, f, indent=2) + + print("Profile saved successfully!") + return profile_data + + +def load_profile(): + profile_file = os.path.join(caltechdata_directory, "profile.json") + try: + with open(profile_file, "r") as f: + return json.load(f) + except FileNotFoundError: + return None + + +def get_or_create_profile(): + profile = load_profile() + if profile: + use_saved = input("Use saved profile? (y/n): ").lower() + if use_saved == "y": + return profile + + print("Creating new profile...") + return save_profile() + + def parse_arguments(): welcome_message() args = {} @@ -222,23 +287,17 @@ def parse_arguments(): break else: print("Invalid input. Please enter a number between 1 and 8.") + # Load or create profile + profile = get_or_create_profile() + if profile: + args["orcid"] = profile["orcid"] + args["family_name"] = profile["family_name"] + args["given_name"] = profile["given_name"] + args["fundingReferences"] = profile["funding_references"] + else: + print("Failed to load or create profile. Exiting.") + return None - while True: - orcid = get_user_input("Enter your ORCID identifier: ") - family_name, given_name = get_names(orcid) - if family_name is not None and given_name is not None: - args["orcid"] = orcid - break # Break out of the loop if names are successfully retrieved - retry = input("Do you want to try again? (y/n): ") - if retry.lower() != "y": - print("Exiting program.") - return - # Optional arguments - num_funding_entries = get_funding_entries() - funding_references = [] - for _ in range(num_funding_entries): - funding_references.append(get_funding_details()) - args["fundingReferences"] = funding_references return args @@ -410,24 +469,48 @@ def parse_args(): parser.add_argument( "-test", action="store_true", help="Use test mode, sets production to False" ) + parser.add_argument( + "--delete-token", action="store_true", help="Delete the saved token." + ) args = parser.parse_args() return args +def normalize_orcid(val): + orcid_urls = ["https://orcid.org/", "http://orcid.org/", "orcid.org/"] + for orcid_url in orcid_urls: + if val.startswith(orcid_url): + val = val[len(orcid_url) :] + break + + val = val.replace("-", "").replace(" ", "") + if len(val) != 16 or not val.isdigit(): + raise ValueError(f"Invalid ORCID identifier: {val}") + return "-".join([val[0:4], val[4:8], val[8:12], val[12:16]]) + + def main(): args = parse_args() + production = not args.test + if args.delete_token: + delete_saved_token() + while True: + choice = get_user_input( + "What would you like to do? (create/edit/profile/exit): " + ).lower() - production = not args.test # Set production to False if -test flag is provided - - choice = get_user_input( - "Do you want to create or edit a CaltechDATA record? (create/edit): " - ).lower() - if choice == "create": - create_record(production) - elif choice == "edit": - edit_record(production) - else: - print("Invalid choice. Please enter 'create' or 'edit'.") + if choice == "create": + create_record(production) + elif choice == "edit": + edit_record(production) + elif choice == "profile": + save_profile() + elif choice == "exit": + break + else: + print( + "Invalid choice. Please enter 'create', 'edit', 'profile', or 'exit'." + ) def create_record(production): diff --git a/codemeta.json b/codemeta.json index aa68675..b409817 100755 --- a/codemeta.json +++ b/codemeta.json @@ -21,8 +21,8 @@ }, { "@type": "Person", - "givenName": "Bhattarai", - "familyName": "Rohan ", + "givenName": "Rohan", + "familyName": "Bhattarai", "affiliation": { "@type": "Organization", "name": "Caltech" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..b61373e --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["setuptools>=61.0", "wheel"] +build-backend = "setuptools.build_meta" diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..cd933c3 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,46 @@ +[build-system] +requires = ["setuptools>=64.0","wheel"] +build-backend = "setuptools.build_meta" + +[metadata] +name = caltechdata_api +version = 1.9.1 +author = Thomas E Morrell, Rohan Bhattarai, Won Elizabeth, Alexander A Abakah +author_email = tmorrell@caltech.edu, aabakah@caltech.edu +description = Python wrapper for CaltechDATA API. +long_description = file: README.md +long_description_content_type = text/markdown +url = https://github.com/caltechlibrary/caltechdata_api +license = MIT +classifiers = + License :: OSI Approved :: MIT License + Programming Language :: Python :: 3 + Programming Language :: Python :: 3.7 + Programming Language :: Python :: 3.8 + Programming Language :: Python :: 3.9 + Programming Language :: Python :: 3.10 + Programming Language :: Python :: Implementation :: CPython + Operating System :: OS Independent + +[options] +packages = find: +python_requires = >=3.6.0 +install_requires = + requests + datacite>1.1.0 + tqdm>=4.62.3 + pyyaml + s3fs + cryptography + s3cmd +include_package_data = True + +[options.packages.find] +exclude = tests + +[options.entry_points] +console_scripts = + caltechdata_api=caltechdata_api.cli:main + +[tool:pytest] +addopts = --verbose diff --git a/setup.py b/setup.py index a4dbcf8..8bf1ba9 100755 --- a/setup.py +++ b/setup.py @@ -1,179 +1,2 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -# Note: To use the 'upload' functionality of this file, you must: -# $ pip install twine - -import io, json -import os -import sys -import glob -from shutil import rmtree - -from setuptools import find_packages, setup, Command - - -def read(fname): - with open(fname, mode="r", encoding="utf-8") as f: - src = f.read() - return src - - -def package_files(package, directory): - os.chdir(package) - paths = glob.glob(directory + "/**", recursive=True) - os.chdir("..") - return paths - - -codemeta_json = "codemeta.json" - -# Let's pickup as much metadata as we need from codemeta.json -with open(codemeta_json, mode="r", encoding="utf-8") as f: - src = f.read() - meta = json.loads(src) - -# Let's make our symvar string -version = meta["version"] - -# Now we need to pull and format our author, author_email strings. -author = "" -author_email = "" -for obj in meta["author"]: - given = obj.get("givenName", "") - family = obj.get("familyName", "") - email = obj.get("email", "") - if len(author) == 0: - author = given + " " + family - else: - author = author + ", " + given + " " + family - if len(author_email) == 0: - author_email = email - else: - author_email = author_email + ", " + email -description = meta["description"] -url = meta["codeRepository"] -download = meta["downloadUrl"] -license = meta["license"] -name = meta["name"] - -REQUIRES_PYTHON = ">=3.6.0" - -# What packages are required for this module to be executed? -REQUIRED = [ - "requests", - "datacite>1.1.0", - "tqdm>=4.62.3", - "pyyaml", - "s3fs", - "cryptography", - "s3cmd", -] - -# What packages are optional? -EXTRAS = { - # 'fancy feature': ['django'], -} - -files = package_files("caltechdata_api", "vocabularies") -files.append("vocabularies.yaml") - -# The rest you shouldn't have to touch too much :) -# ------------------------------------------------ -# Except, perhaps the License and Trove Classifiers! -# If you do change the License, remember to change the Trove Classifier for that! - -here = os.path.abspath(os.path.dirname(__file__)) - -# Import the README and use it as the long-description. -# Note: this will only work if 'README.md' is present in your MANIFEST.in file! -try: - with io.open(os.path.join(here, "README.md"), encoding="utf-8") as f: - long_description = "\n" + f.read() -except FileNotFoundError: - long_description = description - -# Load the package's __version__.py module as a dictionary. -about = {} -if not version: - with open(os.path.join(here, NAME, "__version__.py")) as f: - exec(f.read(), about) -else: - about["__version__"] = version - - -class UploadCommand(Command): - """Support setup.py upload.""" - - description = "Build and publish the package." - user_options = [] - - @staticmethod - def status(s): - """Prints things in bold.""" - print("\033[1m{0}\033[0m".format(s)) - - def initialize_options(self): - pass - - def finalize_options(self): - pass - - def run(self): - try: - self.status("Removing previous builds…") - rmtree(os.path.join(here, "dist")) - except OSError: - pass - - self.status("Building Source and Wheel distribution…") - os.system("{0} setup.py sdist bdist_wheel ".format(sys.executable)) - - self.status("Uploading the package to PyPI via Twine…") - os.system("twine upload dist/*") - - sys.exit() - - -# Where the magic happens: -setup( - name=name, - version=about["__version__"], - description=description, - long_description=long_description, - long_description_content_type="text/markdown", - author=author, - author_email=author_email, - python_requires=REQUIRES_PYTHON, - url=url, - packages=find_packages(exclude=("tests",)), - # If your package is a single module, use this instead of 'packages': - # py_modules=['mypackage'], - # entry_points={ - # 'console_scripts': ['mycli=mymodule:cli'], - # }, - install_requires=REQUIRED, - extras_require=EXTRAS, - include_package_data=True, - package_data={name: files}, - license=license, - classifiers=[ - # Trove classifiers - # Full list: https://pypi.python.org/pypi?%3Aaction=list_classifiers - "License :: OSI Approved :: BSD License", - "Programming Language :: Python", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: Implementation :: CPython", - "Programming Language :: Python :: Implementation :: PyPy", - ], - # $ setup.py publish support. - cmdclass={ - "upload": UploadCommand, - }, - entry_points={ - "console_scripts": [ - "caltechdata_api=caltechdata_api.cli:main", - ], - }, -) +from setuptools import setup +setup()