diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index fcdaa28..c68b6e5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -22,7 +22,7 @@ repos: rev: 'v1.10.1' # Use the sha / tag you want to point at hooks: - id: mypy - additional_dependencies: [types-all] + additional_dependencies: [types-requests] - repo: https://github.com/codespell-project/codespell rev: v2.3.0 hooks: diff --git a/README.md b/README.md index a283125..c25eac4 100644 --- a/README.md +++ b/README.md @@ -2,18 +2,41 @@ Tools to standardize repository settings in a specific GitHub organization. -For the Reich Lab repos, we've decided to: +For the Reich Lab repos, we've decided to apply the following settings to the default +branches (e.g., `main`) of our repos: -* Disallow direct pushes to the main branch -* Allow repo write access to all members of the Reich Lab organization -* Require code reviews before merging to the main branch +* Branch cannot be deleted +* Disallow direct pushes (must open a pull request instead) +* Require at least one reviewer approval before merging a pull request +* Require re-approval when changes are made to a pull request -## Setup for local development + +# Usage + +## Prerequisites + +* Write access to all repos in the reichlab GitHub organization +* A `GITHUB_TOKEN` environment variable that contains a [GitHub personal access token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens) + +## Running the code + +1. Install this Python package via pip: + + ```bash + pip install git+https://github.com/reichlab/standardize-repo-settings.git + ``` + +2. To apply the Reichlab's default branch rulesets to all repos in the Reichlab GitHub organization: + ```bash + add_default_rulesets + ``` + +# Setup for local development The steps below are for setting up a local development environment. This process entails more than just installing the package, because we need to ensure that all developers have a consistent, reproducible environment. -### Assumptions +## Assumptions Developers will be using a Python virtual environment that: @@ -21,14 +44,14 @@ Developers will be using a Python virtual environment that: - contains the dependency versions specified in the "lockfile" (in this case [requirements/requirements-dev.txt](requirements/requirements-dev.txt)). - contains the package installed in ["editable" mode](https://packaging.python.org/en/latest/guides/distributing-packages-using-setuptools/#working-in-development-mode). -### Setup steps +## Setup steps 1. Clone this repository 2. Change to the repo's root directory: ```bash - cd standardize-repo-settings + cd reichlab-repo-utils ``` 3. Make sure the correct version of Python is currently active, and create a Python virtual environment: @@ -61,12 +84,12 @@ Developers will be using a Python virtual environment that: python -m pytest ``` -## Development workflow +# Development workflow Because the package is installed in "editable" mode, you can run the code as though it were a normal Python package, while also being able to make changes and see them immediately. -### Updating dependencies +## Updating dependencies Prerequisites: - [`uv`](https://github.com/astral-sh/uv?tab=readme-ov-file#getting-started) diff --git a/pyproject.toml b/pyproject.toml index 79fbbc5..87b0c0d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [project] -name = "standardize-repo-settings" +name = "reichlab-repo-utils" description = "Standardize GitHub repository settings" license = {text = "MIT License"} readme = "README.md" @@ -11,9 +11,9 @@ classifiers = [ dynamic = ["version"] dependencies = [ - "freezegun", - "structlog", + "requests", "rich", + "structlog", ] [project.optional-dependencies] @@ -22,21 +22,20 @@ dev = [ "pre-commit", "pytest", "ruff", + "types-requests", ] [project.entry-points."console_scripts"] -standardize_repo_settings = "standardize_repo_settings.app:main" +add_default_rulesets = "reichlab_repo_utils.add_repo_rulesets:main" +archive_repos = "reichlab_repo_utils.archive_repos:main" +list_repos = "reichlab_repo_utils.list_repos:main" [build-system] # Minimum requirements for the build system to execute. requires = ["setuptools", "wheel"] [tools.setuptools] -packages = ["standardize_repo_settings"] - -[tool.standardize_repo_settings] -# to write json-formatted logs to disk, uncomment the following line specify the file location -# log_file = "/path/to/log/files/rechlab_python_template.log" +packages = ["reichlab_repo_utils"] [tool.ruff] line-length = 120 @@ -49,4 +48,11 @@ inline-quotes = "double" quote-style = "double" [tool.setuptools.dynamic] -version = {attr = "standardize_repo_settings.__version__"} +version = {attr = "reichlab_repo_utils.__version__"} + +[tool.mypy] +ignore_missing_imports = true + +[[tool.mypy.overrides]] +module = ["rich.*"] +follow_imports = "skip" diff --git a/requirements/requirements-dev.txt b/requirements/requirements-dev.txt index 46c647e..9aad875 100644 --- a/requirements/requirements-dev.txt +++ b/requirements/requirements-dev.txt @@ -1,15 +1,19 @@ # This file was autogenerated by uv via the following command: # uv pip compile pyproject.toml --extra dev -o requirements/requirements-dev.txt +certifi==2024.7.4 + # via requests cfgv==3.4.0 # via pre-commit +charset-normalizer==3.3.2 + # via requests distlib==0.3.8 # via virtualenv filelock==3.14.0 # via virtualenv -freezegun==1.5.1 - # via standardize-repo-settings (pyproject.toml) identify==2.5.36 # via pre-commit +idna==3.7 + # via requests iniconfig==2.0.0 # via pytest markdown-it-py==3.0.0 @@ -17,7 +21,7 @@ markdown-it-py==3.0.0 mdurl==0.1.2 # via markdown-it-py mypy==1.10.0 - # via standardize-repo-settings (pyproject.toml) + # via reichlab-repo-utils (pyproject.toml) mypy-extensions==1.0.0 # via mypy nodeenv==1.8.0 @@ -29,26 +33,30 @@ platformdirs==4.2.1 pluggy==1.5.0 # via pytest pre-commit==3.7.0 - # via standardize-repo-settings (pyproject.toml) + # via reichlab-repo-utils (pyproject.toml) pygments==2.18.0 # via rich pytest==8.2.0 - # via standardize-repo-settings (pyproject.toml) -python-dateutil==2.9.0.post0 - # via freezegun + # via reichlab-repo-utils (pyproject.toml) pyyaml==6.0.1 # via pre-commit +requests==2.32.3 + # via reichlab-repo-utils (pyproject.toml) rich==13.7.1 - # via standardize-repo-settings (pyproject.toml) + # via reichlab-repo-utils (pyproject.toml) ruff==0.4.3 - # via standardize-repo-settings (pyproject.toml) + # via reichlab-repo-utils (pyproject.toml) setuptools==72.1.0 # via nodeenv -six==1.16.0 - # via python-dateutil structlog==24.1.0 - # via standardize-repo-settings (pyproject.toml) + # via reichlab-repo-utils (pyproject.toml) +types-requests==2.32.0.20240712 + # via reichlab-repo-utils (pyproject.toml) typing-extensions==4.11.0 # via mypy +urllib3==2.2.2 + # via + # requests + # types-requests virtualenv==20.26.1 # via pre-commit diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 51efcc8..a77bac8 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -1,18 +1,22 @@ # This file was autogenerated by uv via the following command: # uv pip compile pyproject.toml -o requirements/requirements.txt -freezegun==1.5.1 - # via standardize-repo-settings (pyproject.toml) +certifi==2024.7.4 + # via requests +charset-normalizer==3.3.2 + # via requests +idna==3.7 + # via requests markdown-it-py==3.0.0 # via rich mdurl==0.1.2 # via markdown-it-py pygments==2.18.0 # via rich -python-dateutil==2.9.0.post0 - # via freezegun +requests==2.32.3 + # via reichlab-repo-utils (pyproject.toml) rich==13.7.1 - # via standardize-repo-settings (pyproject.toml) -six==1.16.0 - # via python-dateutil + # via reichlab-repo-utils (pyproject.toml) structlog==24.1.0 - # via standardize-repo-settings (pyproject.toml) + # via reichlab-repo-utils (pyproject.toml) +urllib3==2.2.2 + # via requests diff --git a/src/reichlab_repo_utils/__init__.py b/src/reichlab_repo_utils/__init__.py new file mode 100644 index 0000000..f3373e5 --- /dev/null +++ b/src/reichlab_repo_utils/__init__.py @@ -0,0 +1,152 @@ +__version__ = "0.0.1" + +# add_repo_rulesets.py will apply branch protections to the repos in this list +# branch protections are defined in rulesets/reichlab_default_branch_protections.json +# source: https://docs.google.com/spreadsheets/d/1UaVsqGQ2uyI42t8HWTQjt0MthQJ-o4Yom0-Q2ahBnJc/edit?gid=1230520805#gid=1230520805 +# (any repo with "add branch protections" column = TRUE) +RULESET_REPO_LIST = [ + "container-utils", + "covid-hosp-models", + "covidData", + "distfromq", + "docs.zoltardata", + "election-forecast", + "flu-hosp-models-2021-2022", + "flusion", + "forecast-repository", + "gbq_operational", + "genomicdata", + "hub-infrastructure-experiments", + "idforecastutils", + "jacques", + "jacques-covid", + "llmtime", + "malaria-serology", + "predictability", + "predtimechart", + "pymmwr", + "qenspy", + "qensr", + "rclp", + "sarimaTD", + "sarix-covid", + "simplets", + "timeseriesutils", + "variant-nowcast-hub", + "zoltpy", + "zoltr", + "se-asia-annual-preds", + "Dengue-district-map", + "spatialpred", + "dengueThailand", + "bangkok-forecasting", + "dengue-reporting", + "chikv-inference", + "2016-2017-flu-contest-ensembles", + "delay-analysis-method", + "R6spatialpred", + "dengue-data", + "under-reported", + "delay-analysis-dynamic", + "kcde-via-stacking", + "forecastingForest", + "thaiDhfDwe", + "challenges-inference-filtering", + "thai-dengue-district-challenge", + "delay-analysis-thailand", + "survival-densities", + "dengue-scraps", + "moph-forecast-files", + "wiki", + "pfep", + "hforecast", + "flusense-data", + "covid19ILIUMassCoEModels", + "covid-19-ili-forecasting-models", + "epiWaves", + "wnv", + "hierarchicalGP", + "midas2021", + "covid-forecast-eval-post", + "abc-model-selection", + "conditional-calibration", + "flusion-manuscript", + "pytorch-exploration", + "FluSight-forecast-hub", + "streamlit-flusight", + "predtimechart-s3-example", + "hubverse-cloud-viz", + "trendsEnsemble", + "reichlab-python-template", + "reichlab-repo-utils", + "virus-clade-utils", +] + +# archive_repos.py will archive the repos in this list +# source: https://docs.google.com/spreadsheets/d/1UaVsqGQ2uyI42t8HWTQjt0MthQJ-o4Yom0-Q2ahBnJc/edit?gid=1230520805#gid=1230520805 +# (any repo with candidate_for_archive column = TRUE) +ARCHIVE_REPO_LIST = [ + "2017-2018-cdc-flu-contest", + "2018-2019-cdc-flu-contest", + "activemonitr", + "adaptively-weighted-ensemble", + "ALERT", + "annual-predictions-paper", + "ardfa", + "article-disease-pred-with-kcde", + "bayesian_non_parametric", + "casebot", + "cdcfluforecasts", + "cdcfluutils", + "cdcForecastUtils", + "container-demo-app", + "covid-hosp-forecasts-with-cases", + "covid19-ensemble-methods-manuscript", + "covid19-forecast-evals", + "covid19-forecast-hub-validations", + "d3-foresight", + "dengue-data-stub", + "dengue-ssr-prediction", + "dengue-thailand-2014-forecasts", + "densitystackr", + "diffport", + "ensemble-comparison", + "ensemble-size", + "flu-eda", + "flusight-csv-tools", + "Flusight-forecast-data", + "FluSight-package", + "flusight-test", + "flusurv-forecasts-2020-2021", + "forecast-framework-demos", + "forecastTools", + "foresight-visualization-template", + "german-flu-forecasting", + "hubEnsembles", + "kcde", + "ledge", + "lssm", + "make-example", + "mmwr-week", + "mvtnorm-mod-kcde", + "ncov", + "neural-stack", + "nuxt-forecast-viz", + "online-lag-ensemble", + "pdtmvn", + "pkr", + "proper-scores-comparison", + "pykwalify", + "pylssm", + "reviewMID", + "shiny-predictions", + "ssr-influenza-competition", + "style", + "tracking-ensemble", + "TSIRsim", + "xgboost-mod", + "xgbstack", + "xpull", + "Zoltar-Vizualization", + "duck-hub", +] diff --git a/src/reichlab_repo_utils/add_repo_rulesets.py b/src/reichlab_repo_utils/add_repo_rulesets.py new file mode 100644 index 0000000..1a61c66 --- /dev/null +++ b/src/reichlab_repo_utils/add_repo_rulesets.py @@ -0,0 +1,87 @@ +import importlib +import json +import os +from pathlib import Path + +import requests +import structlog + +from reichlab_repo_utils import RULESET_REPO_LIST +from reichlab_repo_utils.util.logs import setup_logging +from reichlab_repo_utils.util.repo import get_all_repos +from reichlab_repo_utils.util.session import get_session + +setup_logging() +logger = structlog.get_logger() + +GITHUB_ORG = "reichlab" +RULESET_TO_APPLY = "reichlab_default_branch_protections.json" + + +def load_branch_ruleset(filepath: str) -> dict: + """ + Load branch ruleset from a JSON file. + + :param filepath: Path to the JSON file containing the branch ruleset + :return: Dictionary containing the branch ruleset + """ + with open(filepath, "r") as file: + return json.load(file) + + +def apply_branch_ruleset(org_name: str, branch_ruleset: dict, session: requests.Session): + """ + Apply a branch ruleset to every repository in a GitHub organization. + + :param org_name: Name of the GitHub organization + :param branch_ruleset: Dictionary containing the branch ruleset + :param session: Requests session for interacting with the GitHub API + """ + + # Get all repositories in the organization + repos = get_all_repos(org_name, session) + + # Only update repos that are on our list and are not already archived + repos_to_update = [repo for repo in repos if (repo["name"] in RULESET_REPO_LIST and repo["archived"] is False)] + + update_count = 0 + for repo in repos_to_update: + repo_name = repo["name"] + logger.info(repo_name) + branch_protection_url = f"https://api.github.com/repos/{org_name}/{repo_name}/rulesets" + + # Apply the branch ruleset + response = session.post(branch_protection_url, json=branch_ruleset) + if response.ok: + logger.info(f"Successfully applied branch ruleset to {repo_name}") + update_count += 1 + elif response.status_code == 422: + logger.warning( + "Failed to apply branch ruleset (likely because it already exists)", + repo=repo_name, + response=response.json(), + ) + else: + logger.error("Failed to apply branch ruleset", repo=repo_name, response=response.json()) + + logger.info("All rulesets applied", count=update_count) + + +def main(): + org_name = GITHUB_ORG + token = os.getenv("GITHUB_TOKEN") + if not token: + logger.error("GITHUB_TOKEN environment variable is required") + return + + session = get_session(token) + + mod_path = Path(importlib.util.find_spec("reichlab_repo_utils").origin).parent + ruleset_path = mod_path / "rulesets" / RULESET_TO_APPLY + branch_ruleset = load_branch_ruleset(str(ruleset_path)) + + apply_branch_ruleset(org_name, branch_ruleset, session) + + +if __name__ == "__main__": + main() diff --git a/src/reichlab_repo_utils/archive_repos.py b/src/reichlab_repo_utils/archive_repos.py new file mode 100644 index 0000000..f17b4a9 --- /dev/null +++ b/src/reichlab_repo_utils/archive_repos.py @@ -0,0 +1,66 @@ +import os + +import requests +import structlog + +from reichlab_repo_utils import ARCHIVE_REPO_LIST +from reichlab_repo_utils.util.logs import setup_logging +from reichlab_repo_utils.util.repo import get_all_repos +from reichlab_repo_utils.util.session import get_session + +setup_logging() +logger = structlog.get_logger() + +GITHUB_ORG = "reichlab" +RULESET_TO_APPLY = "reichlab_default_branch_protections.json" + + +def archive_repo(org_name: str, session: requests.Session): + """ + Archive repositories in the organization. + + :param org_name: Name of the GitHub organization + :param session: Requests session for interacting with the GitHub API + """ + + # Get all repositories in the organization + repos = get_all_repos(org_name, session) + # Only archive repos that are on our list and are not already archived + repos_to_update = [repo for repo in repos if (repo["name"] in ARCHIVE_REPO_LIST and repo["archived"] is False)] + + # payload for updating the repo to archive status + repo_updates = { + "archived": True, + } + + update_count = 0 + for repo in repos_to_update: + repo_name = repo["name"] + logger.info(repo_name) + repo_url = f"https://api.github.com/repos/{org_name}/{repo_name}" + + # Archive the repo + response = session.patch(repo_url, json=repo_updates) + if response.ok: + logger.info(f"Successfully archived {repo_name}") + update_count += 1 + else: + logger.error("Failed to update repo", repo=repo_name, response=response.json()) + + logger.info("Repository archive complete", count=update_count) + + +def main(): + org_name = GITHUB_ORG + token = os.getenv("GITHUB_TOKEN") + if not token: + logger.error("GITHUB_TOKEN environment variable is required") + return + + session = get_session(token) + + archive_repo(org_name, session) + + +if __name__ == "__main__": + main() diff --git a/src/reichlab_repo_utils/list_repos.py b/src/reichlab_repo_utils/list_repos.py new file mode 100644 index 0000000..b948a6d --- /dev/null +++ b/src/reichlab_repo_utils/list_repos.py @@ -0,0 +1,91 @@ +import os +from itertools import zip_longest +from typing import NamedTuple + +import requests +import structlog +from rich.console import Console +from rich.style import Style +from rich.table import Table + +from reichlab_repo_utils.util.logs import setup_logging +from reichlab_repo_utils.util.repo import get_all_repos +from reichlab_repo_utils.util.session import get_session + +setup_logging() +logger = structlog.get_logger() + +GITHUB_ORG = "reichlab" + + +class OutputColumns(NamedTuple): + name: str + created_at: str + archived: str + visibility: str + id: str + + +def list_repos(org_name: str, session: requests.Session): + """ + Archive repositories in the organization. + + :param org_name: Name of the GitHub organization + :param session: Requests session for interacting with the GitHub API + """ + + # Settings for the output columns when listing repo information + output_column_list = list(OutputColumns._fields) + output_column_colors = ["green", "magenta", "cyan", "blue", "yellow"] + + # Create the output table and columns + console = Console() + table = Table( + title=f"Repositories in the {org_name} GitHub organization", + ) + for col, color in zip_longest(output_column_list, output_column_colors, fillvalue="cyan"): + # add additional attributes, depending on the column + style_kwargs = {} + col_kwargs = {} + if col == "name": + col_kwargs = {"ratio": 4} + style_kwargs = {"link": True} + + style = Style(color=color, **style_kwargs) + table.add_column(col, style=style, **col_kwargs) + + repos = get_all_repos(org_name, session) + repo_count = len(repos) + + for repo in repos: + r = OutputColumns( + name=f"[link={repo.get('html_url')}]{repo.get('name')}[/link]", + created_at=str(repo.get("created_at", "")), + archived=str(repo.get("archived", "")), + visibility=str(repo.get("visibility", "")), + id=str(repo.get("id", "")), + ) + try: + table.add_row(*r) + except Exception as e: + logger.error(f"Error adding row for repo {r.name}: {e}") + + logger.info("Repository report complete", count=repo_count) + + console.print(table) + + +def main(): + org_name = GITHUB_ORG + token = os.getenv("GITHUB_TOKEN") + if not token: + logger.error("GITHUB_TOKEN environment variable is required") + return + + session = get_session(token) + + list_repos(org_name, session) + + +if __name__ == "__main__": + main() diff --git a/src/reichlab_repo_utils/rulesets/reichlab_default_branch_protections.json b/src/reichlab_repo_utils/rulesets/reichlab_default_branch_protections.json new file mode 100644 index 0000000..4a11147 --- /dev/null +++ b/src/reichlab_repo_utils/rulesets/reichlab_default_branch_protections.json @@ -0,0 +1,32 @@ +{ + "name": "reichlab-default-branch-protections", + "target": "branch", + "enforcement": "active", + "conditions": { + "ref_name": { + "exclude": [], + "include": [ + "~DEFAULT_BRANCH" + ] + } + }, + "rules": [ + { + "type": "deletion" + }, + { + "type": "non_fast_forward" + }, + { + "type": "pull_request", + "parameters": { + "required_approving_review_count": 1, + "dismiss_stale_reviews_on_push": true, + "require_code_owner_review": false, + "require_last_push_approval": true, + "required_review_thread_resolution": false + } + } + ], + "bypass_actors": [] +} \ No newline at end of file diff --git a/src/standardize_repo_settings/util/__init__.py b/src/reichlab_repo_utils/util/__init__.py similarity index 100% rename from src/standardize_repo_settings/util/__init__.py rename to src/reichlab_repo_utils/util/__init__.py diff --git a/src/standardize_repo_settings/util/logs.py b/src/reichlab_repo_utils/util/logs.py similarity index 65% rename from src/standardize_repo_settings/util/logs.py rename to src/reichlab_repo_utils/util/logs.py index 25f3021..c2651b9 100644 --- a/src/standardize_repo_settings/util/logs.py +++ b/src/reichlab_repo_utils/util/logs.py @@ -2,25 +2,17 @@ import structlog -import standardize_repo_settings - def add_custom_info(logger, method_name, event_dict): - event_dict["version"] = standardize_repo_settings.__version__ + # placeholder for custom log info return event_dict def setup_logging(): shared_processors = [ add_custom_info, - structlog.processors.TimeStamper(fmt="iso"), + structlog.processors.TimeStamper(fmt="%Y-%m-%d %H:%M:%S"), structlog.processors.add_log_level, - structlog.processors.CallsiteParameterAdder( - [ - structlog.processors.CallsiteParameter.FILENAME, - structlog.processors.CallsiteParameter.FUNC_NAME, - ] - ), ] if sys.stderr.isatty(): diff --git a/src/reichlab_repo_utils/util/repo.py b/src/reichlab_repo_utils/util/repo.py new file mode 100644 index 0000000..78ee348 --- /dev/null +++ b/src/reichlab_repo_utils/util/repo.py @@ -0,0 +1,21 @@ +"""Functions to get information about GitHub repositories.""" + +import requests + + +def get_all_repos(org_name: str, session: requests.Session) -> list[dict]: + """ + Retrieve all repositories from a GitHub organization, handling pagination. + + :param org_name: Name of the GitHub organization + :param session: Requests session for interacting with the GitHub API + :return: List of repositories + """ + repos = [] + repos_url = f"https://api.github.com/orgs/{org_name}/repos" + while repos_url: + response = session.get(repos_url) + response.raise_for_status() + repos.extend(response.json()) + repos_url = response.links.get("next", {}).get("url") + return repos diff --git a/src/reichlab_repo_utils/util/session.py b/src/reichlab_repo_utils/util/session.py new file mode 100644 index 0000000..93a0a1c --- /dev/null +++ b/src/reichlab_repo_utils/util/session.py @@ -0,0 +1,29 @@ +"""Code to handle requests sessions.""" + +import requests +from requests.adapters import HTTPAdapter +from requests.packages.urllib3.util.retry import Retry # type: ignore + + +def get_session(token: str) -> requests.Session: + """Return a requests session with retry logic.""" + + headers = { + "Authorization": f"Bearer {token}", + "Accept": "application/vnd.github.v3+json", + "X-GitHub-Api-Version": "2022-11-28", + } + session = requests.Session() + + # attach a urllib3 retry adapter to the requests session + # https://urllib3.readthedocs.io/en/latest/reference/urllib3.util.html#urllib3.util.retry.Retry + retries = Retry( + total=5, + allowed_methods=frozenset(["GET", "POST", "PATCH"]), + backoff_factor=1, + status_forcelist=[429, 500, 502, 503, 504], + ) + session.mount("https://", HTTPAdapter(max_retries=retries)) + session.headers.update(headers) + + return session diff --git a/src/standardize_repo_settings/__init__.py b/src/standardize_repo_settings/__init__.py deleted file mode 100644 index f102a9c..0000000 --- a/src/standardize_repo_settings/__init__.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = "0.0.1" diff --git a/src/standardize_repo_settings/app.py b/src/standardize_repo_settings/app.py deleted file mode 100644 index b400792..0000000 --- a/src/standardize_repo_settings/app.py +++ /dev/null @@ -1,20 +0,0 @@ -import structlog - -from standardize_repo_settings.util.date import get_current_date -from standardize_repo_settings.util.logs import setup_logging - -setup_logging() -logger = structlog.get_logger() - - -def main(): - """Application entry point.""" - - today = get_current_date() - logger.info("retrieved the date", date=today) - - return f"Hello, today is {today}!" - - -if __name__ == "__main__": - main() diff --git a/src/standardize_repo_settings/util/date.py b/src/standardize_repo_settings/util/date.py deleted file mode 100644 index dacc3ff..0000000 --- a/src/standardize_repo_settings/util/date.py +++ /dev/null @@ -1,15 +0,0 @@ -import datetime - -import structlog - -logger = structlog.get_logger() - - -def get_current_date() -> str: - """Return current date in human-readable format.""" - - logger.info("getting the current date") - current_date = datetime.datetime.now() - formatted_date = current_date.strftime("%B %d, %Y") - - return formatted_date diff --git a/tests/reichlab_python_template/test_app.py b/tests/reichlab_python_template/test_app.py deleted file mode 100644 index 0cb43fc..0000000 --- a/tests/reichlab_python_template/test_app.py +++ /dev/null @@ -1,8 +0,0 @@ -from freezegun import freeze_time -from standardize_repo_settings.app import main - - -@freeze_time("2019-07-13") -def test_main_date(): - output = main() - assert "July 13, 2019" in output diff --git a/tests/reichlab_python_template/unit/util/test_date.py b/tests/reichlab_python_template/unit/util/test_date.py deleted file mode 100644 index cd13b6e..0000000 --- a/tests/reichlab_python_template/unit/util/test_date.py +++ /dev/null @@ -1,10 +0,0 @@ -"""Unit tests for the date module.""" - -from freezegun import freeze_time -from standardize_repo_settings.util.date import get_current_date - - -@freeze_time("2024-01-02") -def test_current_date(): - cd = get_current_date() - assert cd == "January 02, 2024" diff --git a/tests/test_placeholder.py b/tests/test_placeholder.py new file mode 100644 index 0000000..201975f --- /dev/null +++ b/tests/test_placeholder.py @@ -0,0 +1,2 @@ +def test_placeholder(): + pass