From 3985df4d4c83cf846387b6fe6dc28f55dca87ff4 Mon Sep 17 00:00:00 2001 From: Jay Miller Date: Fri, 4 Oct 2024 21:00:53 -0400 Subject: [PATCH 01/10] update issue template update issue template to include start and end dates for conference update conferences to use gh-issues instead of manual parsing update conferences template to use grid and new json methods add conferences.json remove envrc --- .github/ISSUE_TEMPLATE/add_conference.yml | 14 +- .gitignore | 1 + .ruby-version | 2 +- Gemfile.lock | 317 ---------------------- _conferences/__main__.py | 135 ++++----- _data/conferences.json | 23 ++ _data/conferences.yml | 1 - _includes/conferences.html | 17 +- _includes/join_us.html | 31 +-- _includes/support.html | 31 +++ requirements-dev.txt | 1 + support.md | 15 + 12 files changed, 141 insertions(+), 447 deletions(-) delete mode 100644 Gemfile.lock create mode 100644 _data/conferences.json delete mode 100644 _data/conferences.yml create mode 100644 _includes/support.html create mode 100644 support.md diff --git a/.github/ISSUE_TEMPLATE/add_conference.yml b/.github/ISSUE_TEMPLATE/add_conference.yml index 65ee725c..e229b0b8 100644 --- a/.github/ISSUE_TEMPLATE/add_conference.yml +++ b/.github/ISSUE_TEMPLATE/add_conference.yml @@ -19,11 +19,17 @@ body: validations: required: true - type: input - id: conference_dates + id: conference_start_date attributes: - label: Conference Dates - description: The start and end date of the conference. - placeholder: "DD (MMM) -DD MMM YYYY" + label: Conference Start Date + description: The start date of the conference. Use `YEAR-MONTH-DAY` + placeholder: "2024-12-31" + - type: input + id: conference_end_date + attributes: + label: Conference End Date + description: The end date of the conference. The website will no longer show the conference after this date. Use `YEAR-MONTH-DAY` format. + placeholder: "2024-12-31" - type: dropdown id: conference_type attributes: diff --git a/.gitignore b/.gitignore index 03eef33c..cab39672 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ vendor/ 2 test_images *-version +.DS_Store diff --git a/.ruby-version b/.ruby-version index a0891f56..fa7adc7a 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -3.3.4 +3.3.5 diff --git a/Gemfile.lock b/Gemfile.lock deleted file mode 100644 index b131638a..00000000 --- a/Gemfile.lock +++ /dev/null @@ -1,317 +0,0 @@ -GEM - remote: https://rubygems.org/ - specs: - activesupport (7.2.1) - base64 - bigdecimal - concurrent-ruby (~> 1.0, >= 1.3.1) - connection_pool (>= 2.2.5) - drb - i18n (>= 1.6, < 2) - logger (>= 1.4.2) - minitest (>= 5.1) - securerandom (>= 0.3) - tzinfo (~> 2.0, >= 2.0.5) - addressable (2.8.7) - public_suffix (>= 2.0.2, < 7.0) - base64 (0.2.0) - bigdecimal (3.1.8) - coffee-script (2.4.1) - coffee-script-source - execjs - coffee-script-source (1.12.2) - colorator (1.1.0) - commonmarker (0.23.10) - concurrent-ruby (1.3.4) - connection_pool (2.4.1) - csv (3.3.0) - dnsruby (1.72.2) - simpleidn (~> 0.2.1) - drb (2.2.1) - em-websocket (0.5.3) - eventmachine (>= 0.12.9) - http_parser.rb (~> 0) - ethon (0.16.0) - ffi (>= 1.15.0) - eventmachine (1.2.7) - execjs (2.9.1) - faraday (2.12.0) - faraday-net_http (>= 2.0, < 3.4) - json - logger - faraday-net_http (3.3.0) - net-http - ffi (1.17.0-aarch64-linux-gnu) - ffi (1.17.0-aarch64-linux-musl) - ffi (1.17.0-arm-linux-gnu) - ffi (1.17.0-arm-linux-musl) - ffi (1.17.0-arm64-darwin) - ffi (1.17.0-x86-linux-gnu) - ffi (1.17.0-x86-linux-musl) - ffi (1.17.0-x86_64-darwin) - ffi (1.17.0-x86_64-linux-gnu) - ffi (1.17.0-x86_64-linux-musl) - forwardable-extended (2.6.0) - gemoji (4.1.0) - github-pages (232) - github-pages-health-check (= 1.18.2) - jekyll (= 3.10.0) - jekyll-avatar (= 0.8.0) - jekyll-coffeescript (= 1.2.2) - jekyll-commonmark-ghpages (= 0.5.1) - jekyll-default-layout (= 0.1.5) - jekyll-feed (= 0.17.0) - jekyll-gist (= 1.5.0) - jekyll-github-metadata (= 2.16.1) - jekyll-include-cache (= 0.2.1) - jekyll-mentions (= 1.6.0) - jekyll-optional-front-matter (= 0.3.2) - jekyll-paginate (= 1.1.0) - jekyll-readme-index (= 0.3.0) - jekyll-redirect-from (= 0.16.0) - jekyll-relative-links (= 0.6.1) - jekyll-remote-theme (= 0.4.3) - jekyll-sass-converter (= 1.5.2) - jekyll-seo-tag (= 2.8.0) - jekyll-sitemap (= 1.4.0) - jekyll-swiss (= 1.0.0) - jekyll-theme-architect (= 0.2.0) - jekyll-theme-cayman (= 0.2.0) - jekyll-theme-dinky (= 0.2.0) - jekyll-theme-hacker (= 0.2.0) - jekyll-theme-leap-day (= 0.2.0) - jekyll-theme-merlot (= 0.2.0) - jekyll-theme-midnight (= 0.2.0) - jekyll-theme-minimal (= 0.2.0) - jekyll-theme-modernist (= 0.2.0) - jekyll-theme-primer (= 0.6.0) - jekyll-theme-slate (= 0.2.0) - jekyll-theme-tactile (= 0.2.0) - jekyll-theme-time-machine (= 0.2.0) - jekyll-titles-from-headings (= 0.5.3) - jemoji (= 0.13.0) - kramdown (= 2.4.0) - kramdown-parser-gfm (= 1.1.0) - liquid (= 4.0.4) - mercenary (~> 0.3) - minima (= 2.5.1) - nokogiri (>= 1.16.2, < 2.0) - rouge (= 3.30.0) - terminal-table (~> 1.4) - webrick (~> 1.8) - github-pages-health-check (1.18.2) - addressable (~> 2.3) - dnsruby (~> 1.60) - octokit (>= 4, < 8) - public_suffix (>= 3.0, < 6.0) - typhoeus (~> 1.3) - html-pipeline (2.14.3) - activesupport (>= 2) - nokogiri (>= 1.4) - http_parser.rb (0.8.0) - i18n (1.14.6) - concurrent-ruby (~> 1.0) - jekyll (3.10.0) - addressable (~> 2.4) - colorator (~> 1.0) - csv (~> 3.0) - em-websocket (~> 0.5) - i18n (>= 0.7, < 2) - jekyll-sass-converter (~> 1.0) - jekyll-watch (~> 2.0) - kramdown (>= 1.17, < 3) - liquid (~> 4.0) - mercenary (~> 0.3.3) - pathutil (~> 0.9) - rouge (>= 1.7, < 4) - safe_yaml (~> 1.0) - webrick (>= 1.0) - jekyll-avatar (0.8.0) - jekyll (>= 3.0, < 5.0) - jekyll-coffeescript (1.2.2) - coffee-script (~> 2.2) - coffee-script-source (~> 1.12) - jekyll-commonmark (1.4.0) - commonmarker (~> 0.22) - jekyll-commonmark-ghpages (0.5.1) - commonmarker (>= 0.23.7, < 1.1.0) - jekyll (>= 3.9, < 4.0) - jekyll-commonmark (~> 1.4.0) - rouge (>= 2.0, < 5.0) - jekyll-default-layout (0.1.5) - jekyll (>= 3.0, < 5.0) - jekyll-feed (0.17.0) - jekyll (>= 3.7, < 5.0) - jekyll-gist (1.5.0) - octokit (~> 4.2) - jekyll-github-metadata (2.16.1) - jekyll (>= 3.4, < 5.0) - octokit (>= 4, < 7, != 4.4.0) - jekyll-include-cache (0.2.1) - jekyll (>= 3.7, < 5.0) - jekyll-mentions (1.6.0) - html-pipeline (~> 2.3) - jekyll (>= 3.7, < 5.0) - jekyll-optional-front-matter (0.3.2) - jekyll (>= 3.0, < 5.0) - jekyll-paginate (1.1.0) - jekyll-readme-index (0.3.0) - jekyll (>= 3.0, < 5.0) - jekyll-redirect-from (0.16.0) - jekyll (>= 3.3, < 5.0) - jekyll-relative-links (0.6.1) - jekyll (>= 3.3, < 5.0) - jekyll-remote-theme (0.4.3) - addressable (~> 2.0) - jekyll (>= 3.5, < 5.0) - jekyll-sass-converter (>= 1.0, <= 3.0.0, != 2.0.0) - rubyzip (>= 1.3.0, < 3.0) - jekyll-sass-converter (1.5.2) - sass (~> 3.4) - jekyll-seo-tag (2.8.0) - jekyll (>= 3.8, < 5.0) - jekyll-sitemap (1.4.0) - jekyll (>= 3.7, < 5.0) - jekyll-swiss (1.0.0) - jekyll-theme-architect (0.2.0) - jekyll (> 3.5, < 5.0) - jekyll-seo-tag (~> 2.0) - jekyll-theme-cayman (0.2.0) - jekyll (> 3.5, < 5.0) - jekyll-seo-tag (~> 2.0) - jekyll-theme-dinky (0.2.0) - jekyll (> 3.5, < 5.0) - jekyll-seo-tag (~> 2.0) - jekyll-theme-hacker (0.2.0) - jekyll (> 3.5, < 5.0) - jekyll-seo-tag (~> 2.0) - jekyll-theme-leap-day (0.2.0) - jekyll (> 3.5, < 5.0) - jekyll-seo-tag (~> 2.0) - jekyll-theme-merlot (0.2.0) - jekyll (> 3.5, < 5.0) - jekyll-seo-tag (~> 2.0) - jekyll-theme-midnight (0.2.0) - jekyll (> 3.5, < 5.0) - jekyll-seo-tag (~> 2.0) - jekyll-theme-minimal (0.2.0) - jekyll (> 3.5, < 5.0) - jekyll-seo-tag (~> 2.0) - jekyll-theme-modernist (0.2.0) - jekyll (> 3.5, < 5.0) - jekyll-seo-tag (~> 2.0) - jekyll-theme-primer (0.6.0) - jekyll (> 3.5, < 5.0) - jekyll-github-metadata (~> 2.9) - jekyll-seo-tag (~> 2.0) - jekyll-theme-slate (0.2.0) - jekyll (> 3.5, < 5.0) - jekyll-seo-tag (~> 2.0) - jekyll-theme-tactile (0.2.0) - jekyll (> 3.5, < 5.0) - jekyll-seo-tag (~> 2.0) - jekyll-theme-time-machine (0.2.0) - jekyll (> 3.5, < 5.0) - jekyll-seo-tag (~> 2.0) - jekyll-titles-from-headings (0.5.3) - jekyll (>= 3.3, < 5.0) - jekyll-watch (2.2.1) - listen (~> 3.0) - jemoji (0.13.0) - gemoji (>= 3, < 5) - html-pipeline (~> 2.2) - jekyll (>= 3.0, < 5.0) - json (2.7.2) - kramdown (2.4.0) - rexml - kramdown-parser-gfm (1.1.0) - kramdown (~> 2.0) - liquid (4.0.4) - listen (3.9.0) - rb-fsevent (~> 0.10, >= 0.10.3) - rb-inotify (~> 0.9, >= 0.9.10) - logger (1.6.1) - mercenary (0.3.6) - minima (2.5.1) - jekyll (>= 3.5, < 5.0) - jekyll-feed (~> 0.9) - jekyll-seo-tag (~> 2.1) - minitest (5.25.1) - net-http (0.4.1) - uri - nokogiri (1.16.7-aarch64-linux) - racc (~> 1.4) - nokogiri (1.16.7-arm-linux) - racc (~> 1.4) - nokogiri (1.16.7-arm64-darwin) - racc (~> 1.4) - nokogiri (1.16.7-x86-linux) - racc (~> 1.4) - nokogiri (1.16.7-x86_64-darwin) - racc (~> 1.4) - nokogiri (1.16.7-x86_64-linux) - racc (~> 1.4) - octokit (4.25.1) - faraday (>= 1, < 3) - sawyer (~> 0.9) - pathutil (0.16.2) - forwardable-extended (~> 2.6) - public_suffix (5.1.1) - racc (1.8.1) - rb-fsevent (0.11.2) - rb-inotify (0.11.1) - ffi (~> 1.0) - rexml (3.3.7) - rouge (3.30.0) - rubyzip (2.3.2) - safe_yaml (1.0.5) - sass (3.7.4) - sass-listen (~> 4.0.0) - sass-listen (4.0.0) - rb-fsevent (~> 0.9, >= 0.9.4) - rb-inotify (~> 0.9, >= 0.9.7) - sawyer (0.9.2) - addressable (>= 2.3.5) - faraday (>= 0.17.3, < 3) - securerandom (0.3.1) - simpleidn (0.2.3) - terminal-table (1.8.0) - unicode-display_width (~> 1.1, >= 1.1.1) - typhoeus (1.4.1) - ethon (>= 0.9.0) - tzinfo (2.0.6) - concurrent-ruby (~> 1.0) - unicode-display_width (1.8.0) - uri (0.13.1) - webrick (1.8.2) - -PLATFORMS - aarch64-linux - aarch64-linux-gnu - aarch64-linux-musl - arm-linux - arm-linux-gnu - arm-linux-musl - arm64-darwin - x86-linux - x86-linux-gnu - x86-linux-musl - x86_64-darwin - x86_64-linux - x86_64-linux-gnu - x86_64-linux-musl - -DEPENDENCIES - github-pages - jekyll - jekyll-feed - jekyll-remote-theme - jemoji - tzinfo-data - webrick (~> 1.7) - -RUBY VERSION - ruby 3.3.4p94 - -BUNDLED WITH - 2.5.11 diff --git a/_conferences/__main__.py b/_conferences/__main__.py index 8aee6f65..4c68c641 100644 --- a/_conferences/__main__.py +++ b/_conferences/__main__.py @@ -1,83 +1,19 @@ -import os -import re -from datetime import datetime, time -from pathlib import Path +import datetime +import pathlib from urllib.parse import urlparse +from typing import Iterator -import yaml -from github import Auth, Github -from github.Issue import Issue -from github.PaginatedList import PaginatedList +import json +import gh_issues -ROOT = Path(__file__).parent.parent -conferences_path = ROOT / "_data/conferences.yml" - -def create_github_client(): - gh_token = os.getenv("GITHUB_TOKEN", "") - auth = Auth.Token(gh_token) - client = Github(auth=auth) - return client - - -def get_open_issues(gh: Github) -> PaginatedList[Issue]: - repo = gh.get_repo("BlackPythonDevs/blackpythondevs.github.io") - issues = repo.get_issues(state="open", labels=["conference"]) +def get_conference_issues() -> Iterator[gh_issues.Issue]: + query = "repo:blackpythondevs/blackpythondevs.github.io type:issue label:conference" + issues = gh_issues.issues_by_query(query) return issues -def parse_conference_details(issue_body: str) -> dict | None: - # Extract fields from issue body - name_match = re.search( - r"Conference Name(?:\r\n|\n){2}(.*?)(?:\r\n|\n){2}", issue_body - ) - url_match = re.search(r"URL(?:\r\n|\n){2}(.*?)(?:\r\n|\n){2}", issue_body) - dates_match = re.search( - r"Conference Dates(?:\r\n|\n){2}(.*?)(?:\r\n|\n){2}", issue_body - ) - type_match = re.search( - r"Conference Type(?:\r\n|\n){2}(.*?)(?:\r\n|\n){2}", issue_body - ) - location_match = re.search( - r"Conference Location(?:\r\n|\n){2}(.*?)(?:\r\n|\n){2}", issue_body - ) - summary_match = re.search( - r"Summary(?:\r\n|\n){2}(.*?)(?:\r\n|\n){2}", - issue_body, - re.DOTALL, - ) - speaking_match = re.search( - r"Speaking(?:\r\n|\n){2}(.*?)(?:\r\n|\n){2}### Code of Conduct(?:\r\n|\n){2}", - issue_body, - re.DOTALL, - ) - - # Set a default value of None for when the url field isn't as expected - valid_url = normalize_url() if not url_match else normalize_url(url_match[1]) - - if dates_match: - conferenceDates = dates_match[1] - # Parse the end date of the conference - endDateStr = conferenceDates.split("-")[1].strip() - endDate = datetime.strptime(endDateStr, "%d %b %Y") - # Check if the conference end date is greater than today - today = datetime.combine(datetime.now(), time()) - - if endDate >= today: - conference = { - "name": name_match[1], - "url": valid_url, - "dates": dates_match[1], - "type": type_match[1], - "location": location_match[1], - "summary": summary_match[1], - "speaking": speaking_match[1] if speaking_match else "", - } - return conference - return None - - -def normalize_url(url_match: str = None): +def normalize_url(url_match: str = str): valid_url = None # Ensure the url field is not blank and the url matches the regex if url_match is not None and url_match.strip() != "": @@ -85,33 +21,54 @@ def normalize_url(url_match: str = None): # If not, then prepend `https` to the url from the issue body # This guards against the website thinking the passed in url is another page on https://blackpythondevs.com/ parsed_url = urlparse(url_match) + if "http" not in parsed_url.scheme.casefold(): valid_url = f"https://{url_match}" else: valid_url = url_match + return valid_url def write_conferences_to_file(confs: list[dict]): # Write the conferences to the _data/conferences.yml file - with conferences_path.open("w") as f: - yaml.dump(confs, f) + conferences_path.write_text(json.dumps(confs)) -if __name__ == "__main__": - conferences = [] +def __to_conference_date(conference_date: str) -> datetime.date: + return datetime.date.fromisoformat(conference_date) - # Create Github client object - gh_client = create_github_client() - # Get open issues from repo - open_issues: PaginatedList[Issue] = get_open_issues(gh_client) - - # Parse each conference issue so long as it has the "conference" label - for issue in open_issues: - if "conference" in [label.name for label in issue.labels]: - parsed_conf = parse_conference_details(issue_body=issue.body) - if parsed_conf: - conferences.append(parsed_conf) +if __name__ == "__main__": + KEYS = [ + "conference_name", + "url", + "conference_start_date", + "conference_end_date", + "conference_type", + "conference_location", + "summary", + "speaking", + ] - write_conferences_to_file(conferences) + conferences = [] + for _issue in get_conference_issues(): + if not hasattr(_issue, "conference_end_date"): + continue + + if __to_conference_date(_issue.conference_end_date) >= datetime.date.today(): + conferences.append({k: getattr(_issue, k, None) for k in KEYS}) + + ROOT = pathlib.Path(__file__).parent.parent + conferences_path = ROOT.joinpath("_data/conferences.json") + conferences_path.write_text( + json.dumps( + list( + sorted( + conferences, + key=lambda x: __to_conference_date(x["conference_start_date"]), + ) + ), + indent=2, + ) + ) diff --git a/_data/conferences.json b/_data/conferences.json new file mode 100644 index 00000000..074035a5 --- /dev/null +++ b/_data/conferences.json @@ -0,0 +1,23 @@ +[ + { + "conference_name": "PyconUg", + "url": "ug.pycon.org", + "conference_start_date": "2024-10-09", + "conference_end_date": "2024-10-13", + "conference_type": "in-person", + "conference_location": "NWSC International Resource Centre (IREC), Kampala, Uganda", + "summary": "The conference is a five-day event, 9-13 October. PyconUganda inherit this tradition of an annual event that brings together python enthusiasts, tech audiences and technology business in uganda and around the world.", + "speaking": null + }, + { + "conference_name": "PyHo", + "url": "https://twitter.com/pythonho", + "conference_start_date": "2024-10-25", + "conference_end_date": "2024-10-26", + "conference_type": "in-person", + "conference_location": "University of Health and Allied Sciences, Trafalgar, Ho. Ho, Volta Region, Ghana.", + "summary": "PyHo, champion the leverage of Python in education promote diversity and inclusion from underrepresented communities within\nthe/beyond our region. Provide the foundational platform for new and experienced speakers alike to share ideas and resources.", + "speaking": null + } +] + diff --git a/_data/conferences.yml b/_data/conferences.yml deleted file mode 100644 index fe51488c..00000000 --- a/_data/conferences.yml +++ /dev/null @@ -1 +0,0 @@ -[] diff --git a/_includes/conferences.html b/_includes/conferences.html index 3ff5790d..8c6b3802 100644 --- a/_includes/conferences.html +++ b/_includes/conferences.html @@ -1,23 +1,30 @@ {% if site.data.conferences.size > 0 %} +
{% for conference in site.data.conferences %} -

- {{ conference.name }} ({{ conference.dates }}) {% if conference.location != "_No response_" %}- {{ conference.location }}{% endif %} + {{ conference.conference_name }}

-

Type: {{ conference.type }}

+

+ {{ conference.conference_start_date }} - {{ conference.conference_end_date }} + {% if conference.conference_location != "_No response_" %} +
+ {{ conference.conference_location }} +

+ {% endif %} +

Type: {{ conference.conference_type }}

{% if conference.summary != "_No response_" %} {{ conference.summary | markdownify }} {% endif %} - {% if conference.speaking != "_No response_" %} + {% if conference.speaking %}

Speaking:

  • {{ conference.speaking | markdownify }}
  • {% endif %}
    -
    {% endfor %} +
    {% else %} No conferences {% endif %} diff --git a/_includes/join_us.html b/_includes/join_us.html index 49484530..e437f5af 100644 --- a/_includes/join_us.html +++ b/_includes/join_us.html @@ -12,36 +12,7 @@

    Not a member yet?

    -
    -

    Support Black Python Devs

    -
    -
    -
    - - -
    -
    - -
    -
    -
    -
    -

    - Black Python Devs is a Non-Profit, fiscally hosted under the GNOME Foundation. GNOME does take a percentage for operating costs for our account and administration but at least 90% of proceeds goes directly to Black Python Devs General Purpose - Fund as detailed here. -

    -
    +{%- include support.html -%}

    Corporate Sponsors

    diff --git a/_includes/support.html b/_includes/support.html new file mode 100644 index 00000000..c353461e --- /dev/null +++ b/_includes/support.html @@ -0,0 +1,31 @@ +
    +

    Support Black Python Devs

    +
    +
    +
    + + +
    +
    + +
    +
    +
    +
    +

    + Black Python Devs is a Non-Profit, fiscally hosted under the GNOME Foundation. GNOME does take a percentage for operating costs for our account and administration but at least 90% of proceeds goes directly to Black Python Devs General Purpose + Fund as detailed here. +

    +
    +
    diff --git a/requirements-dev.txt b/requirements-dev.txt index dc96a769..e7272c65 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,5 +1,6 @@ black ephemeral_port_reserve +gh-issues pre-commit pytest-pyodide pytest-playwright diff --git a/support.md b/support.md new file mode 100644 index 00000000..3a58142e --- /dev/null +++ b/support.md @@ -0,0 +1,15 @@ +--- +layout: default +lang: en +title: Supporting Black Python Devs +--- + +There are a lot of ways that you can support the Black Python Devs community. + +## Supporting Financially + +Black Python devs uses donations to support local user groups and conferences in communities of Black developers, hosting local Black Python Devs events, and covering our operation costs. You can review our [pitch deck](https://www.canva.com/design/DAGKXBERZ2s/J7Z-F5R2j3bw8Unj4SAeZw/edit?utm_content=DAGKXBERZ2s&utm_campaign=designshare&utm_medium=link2&utm_source=sharebutton) for a detailed view of the efforts and initiatives we support. + +Black Python Devs is a 501c3 under the GNOME Foundation. You can make a tax deductable donation using our CommitChange widget below. + +{%- include support.html -%} From 113e69a98b1a590a28ac9d29fb7da6a384f66dca Mon Sep 17 00:00:00 2001 From: Jay Miller Date: Sat, 5 Oct 2024 15:04:09 -0400 Subject: [PATCH 02/10] update .gitignore with coverage --- .gitignore | 1 + _conferences/__main__.py | 65 ++++++++++++++--------- _data/conferences.json | 5 +- requirements-dev.txt | 1 + tests/test.py | 111 --------------------------------------- 5 files changed, 44 insertions(+), 139 deletions(-) diff --git a/.gitignore b/.gitignore index cab39672..d69e7cee 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,4 @@ vendor/ test_images *-version .DS_Store +.coverage diff --git a/_conferences/__main__.py b/_conferences/__main__.py index 4c68c641..498fc84c 100644 --- a/_conferences/__main__.py +++ b/_conferences/__main__.py @@ -7,39 +7,39 @@ import gh_issues -def get_conference_issues() -> Iterator[gh_issues.Issue]: - query = "repo:blackpythondevs/blackpythondevs.github.io type:issue label:conference" +QUERY = "repo:blackpythondevs/blackpythondevs.github.io type:issue label:conference" + + +def get_conference_issues( + query: str = QUERY, +) -> Iterator[gh_issues.Issue]: # pragma no cover issues = gh_issues.issues_by_query(query) return issues -def normalize_url(url_match: str = str): - valid_url = None - # Ensure the url field is not blank and the url matches the regex - if url_match is not None and url_match.strip() != "": - # Parse the url and see if a scheme (`https`) is included in it - # If not, then prepend `https` to the url from the issue body - # This guards against the website thinking the passed in url is another page on https://blackpythondevs.com/ +def normalize_url(url_match: str | None) -> str | None: + """ + Parse the url and see if a scheme (`https`) is included in it. + If not, then prepend `https` to the url from the issue body + + This guards against the website thinking the passed in url is another page on https://blackpythondevs.com/ + """ + if url_match: parsed_url = urlparse(url_match) if "http" not in parsed_url.scheme.casefold(): - valid_url = f"https://{url_match}" + return f"https://{url_match}" else: - valid_url = url_match - - return valid_url - - -def write_conferences_to_file(confs: list[dict]): - # Write the conferences to the _data/conferences.yml file - conferences_path.write_text(json.dumps(confs)) + return url_match def __to_conference_date(conference_date: str) -> datetime.date: return datetime.date.fromisoformat(conference_date) -if __name__ == "__main__": +def parse_conference(issue: gh_issues.Issue) -> dict[str, str | None]: + """convert an issue to a dictionary of parsed data""" + KEYS = [ "conference_name", "url", @@ -51,16 +51,31 @@ def __to_conference_date(conference_date: str) -> datetime.date: "speaking", ] - conferences = [] - for _issue in get_conference_issues(): - if not hasattr(_issue, "conference_end_date"): - continue + _issue = {k: getattr(issue, k, None) for k in KEYS} + _issue["url"] = normalize_url(_issue.get("url", None)) + return _issue + + +def _validate_issue(issue: gh_issues.Issue, date_to_check: str): + """Validate an issue based on its `date_to_check`""" + if not (valid_date := getattr(issue, date_to_check, False)): + return False + else: + return __to_conference_date(valid_date) >= datetime.date.today() + + +def build_conferences() -> list[dict[str, str | None]]: # pragma: no cover + return [ + parse_conference(issue) + for issue in get_conference_issues() + if _validate_issue(issue, "conference_end_date") + ] - if __to_conference_date(_issue.conference_end_date) >= datetime.date.today(): - conferences.append({k: getattr(_issue, k, None) for k in KEYS}) +if __name__ == "__main__": # pragma: no cover ROOT = pathlib.Path(__file__).parent.parent conferences_path = ROOT.joinpath("_data/conferences.json") + conferences = build_conferences() conferences_path.write_text( json.dumps( list( diff --git a/_data/conferences.json b/_data/conferences.json index 074035a5..01b3f082 100644 --- a/_data/conferences.json +++ b/_data/conferences.json @@ -1,7 +1,7 @@ [ { "conference_name": "PyconUg", - "url": "ug.pycon.org", + "url": "https://ug.pycon.org", "conference_start_date": "2024-10-09", "conference_end_date": "2024-10-13", "conference_type": "in-person", @@ -19,5 +19,4 @@ "summary": "PyHo, champion the leverage of Python in education promote diversity and inclusion from underrepresented communities within\nthe/beyond our region. Provide the foundational platform for new and experienced speakers alike to share ideas and resources.", "speaking": null } -] - +] \ No newline at end of file diff --git a/requirements-dev.txt b/requirements-dev.txt index e7272c65..5dd0782d 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -2,6 +2,7 @@ black ephemeral_port_reserve gh-issues pre-commit +pytest-mock pytest-pyodide pytest-playwright pytest-xprocess diff --git a/tests/test.py b/tests/test.py index 919c3d07..c5cc2f9f 100644 --- a/tests/test.py +++ b/tests/test.py @@ -2,7 +2,6 @@ import pytest from xprocess import ProcessStarter -from _conferences.__main__ import parse_conference_details from playwright.sync_api import Page, expect, sync_playwright @@ -100,113 +99,3 @@ def test_mailto_bpdevs(page_url: tuple[Page, str]) -> None: page.goto(live_server_url) mailto = page.get_by_role("link", name="email") expect(mailto).to_have_attribute("href", "mailto:contact@blackpythondevs.com") - - -def test_conference_parsing_valid_url(): - example_conf_issue = """### Conference Name - -Test Conference Title - -### URL - -https://microsoft.com - -### Conference Dates - -10 - 15 Sep 2050 - -### Conference Type - -both - -### Conference Location - -Redmond, WA, USA - -### Summary - -Test Conference Summary - -### Speaking - -* [Satya Nadella](https://www.linkedin.com/in/satyanadella/) -""" - expected_name = "Test Conference Title" - expected_url = "https://microsoft.com" - parsed_conf = parse_conference_details(issue_body=example_conf_issue) - - assert parsed_conf["name"] == expected_name - assert parsed_conf["url"] == expected_url - - -def test_conference_parsing_logic_no_url_scheme(): - example_conf_issue = """### Conference Name - -Test Conference Title - -### URL - -microsoft.com - -### Conference Dates - -10 - 15 Sep 2050 - -### Conference Type - -both - -### Conference Location - -Redmond, WA, USA - -### Summary - -Test Conference Summary - -### Speaking - -* [Satya Nadella](https://www.linkedin.com/in/satyanadella/) -""" - expected_name = "Test Conference Title" - expected_url = "https://microsoft.com" - parsed_conf = parse_conference_details(issue_body=example_conf_issue) - - assert parsed_conf["name"] == expected_name - assert parsed_conf["url"] == expected_url - - -def test_conference_parsing_logic_no_url(): - example_conf_issue = """### Conference Name - -Test Conference Title - -### URL - - -### Conference Dates - -10 - 15 Sep 2050 - -### Conference Type - -both - -### Conference Location - -Redmond, WA, USA - -### Summary - -Test Conference Summary - -### Speaking - -* [Satya Nadella](https://www.linkedin.com/in/satyanadella/) -""" - expected_name = "Test Conference Title" - expected_url = None - parsed_conf = parse_conference_details(issue_body=example_conf_issue) - - assert parsed_conf["name"] == expected_name - assert parsed_conf["url"] == expected_url From 55a8dedde7033b89a8a2d5e234f9204440ddb798 Mon Sep 17 00:00:00 2001 From: Jay Miller Date: Fri, 4 Oct 2024 21:00:53 -0400 Subject: [PATCH 03/10] update issue template update issue template to include start and end dates for conference update conferences to use gh-issues instead of manual parsing update conferences template to use grid and new json methods add conferences.json --- _conferences/__main__.py | 5 +++++ _data/conferences.json | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/_conferences/__main__.py b/_conferences/__main__.py index 498fc84c..dd0e034d 100644 --- a/_conferences/__main__.py +++ b/_conferences/__main__.py @@ -33,6 +33,11 @@ def normalize_url(url_match: str | None) -> str | None: return url_match +def write_conferences_to_file(confs: list[dict]): + # Write the conferences to the _data/conferences.yml file + conferences_path.write_text(json.dumps(confs)) + + def __to_conference_date(conference_date: str) -> datetime.date: return datetime.date.fromisoformat(conference_date) diff --git a/_data/conferences.json b/_data/conferences.json index 01b3f082..717ee7b9 100644 --- a/_data/conferences.json +++ b/_data/conferences.json @@ -1,7 +1,7 @@ [ { "conference_name": "PyconUg", - "url": "https://ug.pycon.org", + "url": "ug.pycon.org", "conference_start_date": "2024-10-09", "conference_end_date": "2024-10-13", "conference_type": "in-person", @@ -19,4 +19,4 @@ "summary": "PyHo, champion the leverage of Python in education promote diversity and inclusion from underrepresented communities within\nthe/beyond our region. Provide the foundational platform for new and experienced speakers alike to share ideas and resources.", "speaking": null } -] \ No newline at end of file +] From 5c633bd0dbb776a03ea90f421217615a2bd50465 Mon Sep 17 00:00:00 2001 From: Jay Miller Date: Sat, 5 Oct 2024 15:13:28 -0400 Subject: [PATCH 04/10] update tests with dedicated conferences page --- tests/tests_conferences.py | 91 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 tests/tests_conferences.py diff --git a/tests/tests_conferences.py b/tests/tests_conferences.py new file mode 100644 index 00000000..6dfd7711 --- /dev/null +++ b/tests/tests_conferences.py @@ -0,0 +1,91 @@ +import json +import pytest +import datetime +from gh_issues import Repo, Issue +import _conferences.__main__ as conferences + +tomorrow = datetime.date.today() + datetime.timedelta(days=1) +yesterday = datetime.date.today() + datetime.timedelta(days=-1) + +TEST_ISSUE_TEXT = f""" +## conference_name + +New Conference + +## url + +https://pycon.us + +## Conference Start Date + +{yesterday.isoformat()} + + +## Conference End Date + +{tomorrow.isoformat()} + +## Conference Type + +online + +## Summary + +This is a test + +## Speaking + +## Ignore This + +This is bad +""" + +TEST_ISSUE = Issue( + repo=Repo("blackpythondevs", "blackpythondevs.github.io"), + issue={"body": TEST_ISSUE_TEXT}, +) + + +@pytest.mark.parametrize("test_url", ["pycon.us", "https://pycon.us"]) +def test_normalize_url(test_url: str): + """ + Tests that urls are valid URLs with https:// protocols + """ + + assert conferences.normalize_url(test_url) == "https://pycon.us" + + +def tests___to_conference_date_converts_to_datetime(): + assert conferences.__to_conference_date("2024-01-01") == datetime.date(2024, 1, 1) + + +def tests_parse_conference(): + """ + Tests when given an Issue it is converted to the only have the keys requested + """ + test_dictionary = { + "conference_name": "New Conference", + "url": "https://pycon.us", + "conference_start_date": yesterday.isoformat(), + "conference_end_date": tomorrow.isoformat(), + "conference_type": "online", + "conference_location": None, # This was omitted from the value + "summary": "This is a test", + "speaking": None, + } + + assert conferences.parse_conference(TEST_ISSUE) == test_dictionary + + +@pytest.mark.parametrize( + "check_value, asserted_value", + [ + ("conference_start_date", False), + ("conference_end_date", True), + ("missing_value", False), + ], +) +def tests_validate_issue(check_value, asserted_value): + """tests the date validator againse yesterday value (fail) and tomorrow value (pass)""" + + assert conferences._validate_issue(TEST_ISSUE, check_value) == asserted_value From ad2d4a9a34035321c0fcaaa460034835ce38ef25 Mon Sep 17 00:00:00 2001 From: Jay Miller Date: Sat, 5 Oct 2024 15:18:38 -0400 Subject: [PATCH 05/10] remove support (working copy) --- support.md | 15 --------------- 1 file changed, 15 deletions(-) delete mode 100644 support.md diff --git a/support.md b/support.md deleted file mode 100644 index 3a58142e..00000000 --- a/support.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -layout: default -lang: en -title: Supporting Black Python Devs ---- - -There are a lot of ways that you can support the Black Python Devs community. - -## Supporting Financially - -Black Python devs uses donations to support local user groups and conferences in communities of Black developers, hosting local Black Python Devs events, and covering our operation costs. You can review our [pitch deck](https://www.canva.com/design/DAGKXBERZ2s/J7Z-F5R2j3bw8Unj4SAeZw/edit?utm_content=DAGKXBERZ2s&utm_campaign=designshare&utm_medium=link2&utm_source=sharebutton) for a detailed view of the efforts and initiatives we support. - -Black Python Devs is a 501c3 under the GNOME Foundation. You can make a tax deductable donation using our CommitChange widget below. - -{%- include support.html -%} From eadfe6f84a5b85de6f24c07d3b4ee295126b82a7 Mon Sep 17 00:00:00 2001 From: Jay Miller Date: Sat, 5 Oct 2024 15:25:16 -0400 Subject: [PATCH 06/10] remove support outside of scope of pr --- _includes/support.html | 31 ------------------------------- 1 file changed, 31 deletions(-) delete mode 100644 _includes/support.html diff --git a/_includes/support.html b/_includes/support.html deleted file mode 100644 index c353461e..00000000 --- a/_includes/support.html +++ /dev/null @@ -1,31 +0,0 @@ -
    -

    Support Black Python Devs

    -
    -
    -
    - - -
    -
    - -
    -
    -
    -
    -

    - Black Python Devs is a Non-Profit, fiscally hosted under the GNOME Foundation. GNOME does take a percentage for operating costs for our account and administration but at least 90% of proceeds goes directly to Black Python Devs General Purpose - Fund as detailed here. -

    -
    -
    From 732770a8e6543410f5df0f0f3b80998ab4cb4c19 Mon Sep 17 00:00:00 2001 From: Jay Miller Date: Sat, 5 Oct 2024 15:35:30 -0400 Subject: [PATCH 07/10] revert support changes --- .github/workflows/conference.yml | 4 ++-- _data/conferences.json | 2 +- _includes/conferences.html | 2 +- _includes/join_us.html | 31 ++++++++++++++++++++++++++++++- tests/tests_conferences.py | 3 +-- 5 files changed, 35 insertions(+), 7 deletions(-) diff --git a/.github/workflows/conference.yml b/.github/workflows/conference.yml index 11450680..7c81feb6 100644 --- a/.github/workflows/conference.yml +++ b/.github/workflows/conference.yml @@ -66,10 +66,10 @@ jobs: - name: Setup Python environment uses: actions/setup-python@v4 with: - python-version: "3.11" + python-version: "3.12" - name: Install dependencies - run: pip install PyGithub PyYAML pre-commit + run: pip install -r requirements-dev.txt - name: Run script env: diff --git a/_data/conferences.json b/_data/conferences.json index 717ee7b9..b86f942f 100644 --- a/_data/conferences.json +++ b/_data/conferences.json @@ -1,7 +1,7 @@ [ { "conference_name": "PyconUg", - "url": "ug.pycon.org", + "url": "https://ug.pycon.org", "conference_start_date": "2024-10-09", "conference_end_date": "2024-10-13", "conference_type": "in-person", diff --git a/_includes/conferences.html b/_includes/conferences.html index 8c6b3802..d9e93933 100644 --- a/_includes/conferences.html +++ b/_includes/conferences.html @@ -3,7 +3,7 @@ {% for conference in site.data.conferences %}

    - {{ conference.conference_name }} + {{ conference.conference_name }}

    {{ conference.conference_start_date }} - {{ conference.conference_end_date }} diff --git a/_includes/join_us.html b/_includes/join_us.html index e437f5af..49484530 100644 --- a/_includes/join_us.html +++ b/_includes/join_us.html @@ -12,7 +12,36 @@

    Not a member yet?

    -{%- include support.html -%} +
    +

    Support Black Python Devs

    +
    +
    +
    + + +
    +
    + +
    +
    +
    +
    +

    + Black Python Devs is a Non-Profit, fiscally hosted under the GNOME Foundation. GNOME does take a percentage for operating costs for our account and administration but at least 90% of proceeds goes directly to Black Python Devs General Purpose + Fund as detailed here. +

    +

    Corporate Sponsors

    diff --git a/tests/tests_conferences.py b/tests/tests_conferences.py index 6dfd7711..98e3cfc2 100644 --- a/tests/tests_conferences.py +++ b/tests/tests_conferences.py @@ -1,4 +1,3 @@ -import json import pytest import datetime from gh_issues import Repo, Issue @@ -25,7 +24,7 @@ {tomorrow.isoformat()} -## Conference Type +## Conference Type online From 338c5ec7e141085d5c1d1a1c9e07d5c1f19d4b2f Mon Sep 17 00:00:00 2001 From: Alex Oladele Date: Sat, 5 Oct 2024 19:12:32 -0400 Subject: [PATCH 08/10] Fix conference scheme if its valid but not "https" Address the case where the conference url scheme is a valid protocol (such as ftp, http, ssh), but is not "https". We replace the existing scheme with the one we want --- _conferences/__main__.py | 198 ++++++++++++++++++++------------------- 1 file changed, 104 insertions(+), 94 deletions(-) diff --git a/_conferences/__main__.py b/_conferences/__main__.py index dd0e034d..ff35e33b 100644 --- a/_conferences/__main__.py +++ b/_conferences/__main__.py @@ -1,94 +1,104 @@ -import datetime -import pathlib -from urllib.parse import urlparse -from typing import Iterator - -import json -import gh_issues - - -QUERY = "repo:blackpythondevs/blackpythondevs.github.io type:issue label:conference" - - -def get_conference_issues( - query: str = QUERY, -) -> Iterator[gh_issues.Issue]: # pragma no cover - issues = gh_issues.issues_by_query(query) - return issues - - -def normalize_url(url_match: str | None) -> str | None: - """ - Parse the url and see if a scheme (`https`) is included in it. - If not, then prepend `https` to the url from the issue body - - This guards against the website thinking the passed in url is another page on https://blackpythondevs.com/ - """ - if url_match: - parsed_url = urlparse(url_match) - - if "http" not in parsed_url.scheme.casefold(): - return f"https://{url_match}" - else: - return url_match - - -def write_conferences_to_file(confs: list[dict]): - # Write the conferences to the _data/conferences.yml file - conferences_path.write_text(json.dumps(confs)) - - -def __to_conference_date(conference_date: str) -> datetime.date: - return datetime.date.fromisoformat(conference_date) - - -def parse_conference(issue: gh_issues.Issue) -> dict[str, str | None]: - """convert an issue to a dictionary of parsed data""" - - KEYS = [ - "conference_name", - "url", - "conference_start_date", - "conference_end_date", - "conference_type", - "conference_location", - "summary", - "speaking", - ] - - _issue = {k: getattr(issue, k, None) for k in KEYS} - _issue["url"] = normalize_url(_issue.get("url", None)) - return _issue - - -def _validate_issue(issue: gh_issues.Issue, date_to_check: str): - """Validate an issue based on its `date_to_check`""" - if not (valid_date := getattr(issue, date_to_check, False)): - return False - else: - return __to_conference_date(valid_date) >= datetime.date.today() - - -def build_conferences() -> list[dict[str, str | None]]: # pragma: no cover - return [ - parse_conference(issue) - for issue in get_conference_issues() - if _validate_issue(issue, "conference_end_date") - ] - - -if __name__ == "__main__": # pragma: no cover - ROOT = pathlib.Path(__file__).parent.parent - conferences_path = ROOT.joinpath("_data/conferences.json") - conferences = build_conferences() - conferences_path.write_text( - json.dumps( - list( - sorted( - conferences, - key=lambda x: __to_conference_date(x["conference_start_date"]), - ) - ), - indent=2, - ) - ) +import datetime +import json +import pathlib +from typing import Iterator +from urllib.parse import urlparse + +import gh_issues + +QUERY = "repo:blackpythondevs/blackpythondevs.github.io type:issue label:conference" + + +def get_conference_issues( + query: str = QUERY, +) -> Iterator[gh_issues.Issue]: # pragma no cover + issues = gh_issues.issues_by_query(query) + return issues + + +def normalize_url(url_match: str | None) -> str | None: + """ + Parse the url and see if a scheme (`https`) is included in it. + If not, then prepend `https` to the url from the issue body + + This guards against the website thinking the passed in url is another page on https://blackpythondevs.com/ + """ + if url_match: + parsed_url = urlparse(url_match) + url_scheme = parsed_url.scheme + + # If "https" is already the scheme, then we're good and don't need to do anything else + if url_scheme == "https": + return url_match + + # If the scheme is not "https", then we need to prepend "https" to the url + fixed_url = None + + if url_scheme.strip() == "": + fixed_url = f"https://{url_match}" + else: + # If the scheme is a valid protocol (ftp, http, etc.), + # but not "https", then we need to replace it with "https" + fixed_url = url_match.replace(parsed_url.scheme, "https") + return fixed_url + + +def write_conferences_to_file(confs: list[dict]): + # Write the conferences to the _data/conferences.yml file + conferences_path.write_text(json.dumps(confs)) + + +def __to_conference_date(conference_date: str) -> datetime.date: + return datetime.date.fromisoformat(conference_date) + + +def parse_conference(issue: gh_issues.Issue) -> dict[str, str | None]: + """convert an issue to a dictionary of parsed data""" + + KEYS = [ + "conference_name", + "url", + "conference_start_date", + "conference_end_date", + "conference_type", + "conference_location", + "summary", + "speaking", + ] + + _issue = {k: getattr(issue, k, None) for k in KEYS} + _issue["url"] = normalize_url(_issue.get("url", None)) + return _issue + + +def _validate_issue(issue: gh_issues.Issue, date_to_check: str) -> bool: + """Validate an issue based on its `date_to_check`""" + if not (valid_date := getattr(issue, date_to_check, False)): + return False + else: + return __to_conference_date(valid_date) >= datetime.date.today() + + +def build_conferences() -> list[dict[str, str | None]]: # pragma: no cover + return [ + parse_conference(issue) + for issue in get_conference_issues() + if _validate_issue(issue, "conference_end_date") + ] + + +if __name__ == "__main__": # pragma: no cover + ROOT = pathlib.Path(__file__).parent.parent + conferences_path = ROOT.joinpath("_data/conferences.json") + conferences = build_conferences() + conferences_path.write_text( + json.dumps( + list( + sorted( + conferences, + key=lambda x: __to_conference_date(x["conference_start_date"]), + ) + ), + indent=2, + ) + ) From 99c3dbc78140146cd454d6e6842000af13fc1903 Mon Sep 17 00:00:00 2001 From: Alex Oladele Date: Sat, 5 Oct 2024 20:10:48 -0400 Subject: [PATCH 09/10] Update normalize_url test Add another test case --- tests/tests_conferences.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/tests_conferences.py b/tests/tests_conferences.py index 98e3cfc2..2fc551b0 100644 --- a/tests/tests_conferences.py +++ b/tests/tests_conferences.py @@ -45,7 +45,7 @@ ) -@pytest.mark.parametrize("test_url", ["pycon.us", "https://pycon.us"]) +@pytest.mark.parametrize("test_url", ["pycon.us", "https://pycon.us", "ftp://pycon.us"]) def test_normalize_url(test_url: str): """ Tests that urls are valid URLs with https:// protocols @@ -85,6 +85,6 @@ def tests_parse_conference(): ], ) def tests_validate_issue(check_value, asserted_value): - """tests the date validator againse yesterday value (fail) and tomorrow value (pass)""" + """tests the date validator against yesterday value (fail) and tomorrow value (pass)""" assert conferences._validate_issue(TEST_ISSUE, check_value) == asserted_value From 36acfe7128fb88c0ea0da6046f5c399ede442b45 Mon Sep 17 00:00:00 2001 From: Alex Oladele Date: Sat, 5 Oct 2024 21:52:34 -0400 Subject: [PATCH 10/10] Update _conferences/__main__.py Co-authored-by: Jay Miller --- _conferences/__main__.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/_conferences/__main__.py b/_conferences/__main__.py index b98c03c9..cfc7b871 100644 --- a/_conferences/__main__.py +++ b/_conferences/__main__.py @@ -32,15 +32,12 @@ def normalize_url(url_match: str | None) -> str | None: return url_match # If the scheme is not "https", then we need to prepend "https" to the url - fixed_url = None - if url_scheme.strip() == "": - fixed_url = f"https://{url_match}" + return f"https://{url_match}" else: # If the scheme is a valid protocol (ftp, http, etc.), # but not "https", then we need to replace it with "https" - fixed_url = url_match.replace(parsed_url.scheme, "https") - return fixed_url + return url_match.replace(parsed_url.scheme, "https") def write_conferences_to_file(confs: list[dict]):