From b2521b0e295e4240856d6eba109cabd17b862ac5 Mon Sep 17 00:00:00 2001 From: Luca Fabbri Date: Mon, 29 May 2023 09:28:10 +0200 Subject: [PATCH 1/7] WIP for calendar scraping --- haunts/cli.py | 5 +++- haunts/download.py | 59 +++++++++++++++++++++++++++++++++++++++++++ haunts/ini.py | 4 +++ haunts/spreadsheet.py | 10 ++++++-- 4 files changed, 75 insertions(+), 3 deletions(-) create mode 100644 haunts/download.py diff --git a/haunts/cli.py b/haunts/cli.py index 3115fc6..20d1683 100644 --- a/haunts/cli.py +++ b/haunts/cli.py @@ -13,6 +13,7 @@ from .spreadsheet import sync_report from .report import report from . import actions +from .download import extract_events @click.command() @@ -35,7 +36,7 @@ @click.option( "--execute", "-e", - type=click.Choice(["sync", "report"], case_sensitive=False), + type=click.Choice(["sync", "report", "read"], case_sensitive=False), help="select which action to execute.", show_default=True, default="sync", @@ -144,6 +145,8 @@ def main( ) elif execute == "report": report(config_dir, sheet, days=day, projects=project, overtime=overtime) + elif execute == "read": + extract_events(config_dir, sheet) return 0 diff --git a/haunts/download.py b/haunts/download.py new file mode 100644 index 0000000..1b779d5 --- /dev/null +++ b/haunts/download.py @@ -0,0 +1,59 @@ +from googleapiclient.discovery import build +from datetime import datetime, timedelta + +from .credentials import get_credentials +from .spreadsheet import get_calendars +from .spreadsheet import SCOPES as SPREADSHEET_SCOPES +from .calendars import SCOPES as CALENDAR_SCOPES + + +def get_events(events_service, calendar_id, date): + start_datetime = datetime.combine(date, datetime.min.time()).isoformat() + "Z" + end_datetime = ( + datetime.combine(date, datetime.min.time()) + timedelta(days=1) + ).isoformat() + "Z" + events_result = events_service.list( + calendarId=calendar_id, + timeMin=start_datetime, + timeMax=end_datetime, + singleEvents=True, + orderBy="startTime", + ).execute() + events = events_result.get("items", []) + return events + + +def extract_events(config_dir, sheet): + calendar_credentials = get_credentials( + config_dir, CALENDAR_SCOPES, "calendars-token.json" + ) + spreadsheeet_credentials = get_credentials( + config_dir, SPREADSHEET_SCOPES, "sheets-token.json" + ) + calendar_service = build("calendar", "v3", credentials=calendar_credentials) + spreadsheet_service = build("sheets", "v4", credentials=spreadsheeet_credentials) + + date_to_check = datetime( + year=2023, month=5, day=21 + ).date() # Replace with the desired date + + events_service = calendar_service.events() + sheet_service = spreadsheet_service.spreadsheets() + + configued_calendars = get_calendars(sheet_service, ignore_alias=True) + print(configued_calendars) + + all_events = [] + for calendar_id in configued_calendars.values(): + print(f"checking {calendar_id}") + events = get_events(events_service, calendar_id, date_to_check) + all_events.extend(events) + + for event in all_events: + event_summary = event.get("summary", "No summary") + start_time = event["start"].get("dateTime", event["start"].get("date")) + end_time = event["end"].get("dateTime", event["end"].get("date")) + print(f"Summary: {event_summary}") + print(f"Start Time: {start_time}") + print(f"End Time: {end_time}") + print("---") diff --git a/haunts/ini.py b/haunts/ini.py index 597afcd..88d4abb 100644 --- a/haunts/ini.py +++ b/haunts/ini.py @@ -21,6 +21,10 @@ # Overtime start date in HH:MM format # Default is empty: no overtime # OVERTIME_FROM=20:00 + +# User email. +# Required for --execute read +# USER_EMAIL= """ parser = configparser.RawConfigParser(allow_no_value=True) diff --git a/haunts/spreadsheet.py b/haunts/spreadsheet.py index 6a736cc..3d48d63 100644 --- a/haunts/spreadsheet.py +++ b/haunts/spreadsheet.py @@ -231,7 +231,8 @@ def sync_events( ) -def get_calendars(sheet): +def get_calendars(sheet, ignore_alias=False): + """In case ignore_alias is true, only the first occurence of a calendar is returned.""" RANGE = f"{get('CONTROLLER_SHEET_NAME', 'config')}!A2:B" calendars = ( sheet.values() @@ -239,7 +240,12 @@ def get_calendars(sheet): .execute() ) values = calendars.get("values", []) - return {alias: id for [id, alias] in values} + configured_calendars = {} + for id, alias in values: + if ignore_alias and id in configured_calendars: + continue + configured_calendars[alias] = id + return configured_calendars def sync_report(config_dir, month, days=[], projects=[], allowed_actions=[]): From d3390538d181c9dee3109fc8360f3762667693e5 Mon Sep 17 00:00:00 2001 From: Luca Fabbri Date: Sat, 3 Jun 2023 10:54:38 +0200 Subject: [PATCH 2/7] Fixed timezone creation of full day events --- haunts/calendars.py | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/haunts/calendars.py b/haunts/calendars.py index 6dadd18..ae0fbc9 100644 --- a/haunts/calendars.py +++ b/haunts/calendars.py @@ -34,9 +34,10 @@ def create_event(config_dir, calendar, date, summary, details, length, from_time from_time = from_time or get("START_TIME", "09:00") start = datetime.datetime.strptime( - f"{date.strftime('%Y-%m-%d')}T{from_time}:00{LOCAL_TIMEZONE}", - "%Y-%m-%dT%H:%M:%S%z", + f"{date.strftime('%Y-%m-%d')}T{from_time}:00Z", + "%Y-%m-%dT%H:%M:%SZ", ) + print(start) startParams = None endParams = None @@ -49,19 +50,27 @@ def create_event(config_dir, calendar, date, summary, details, length, from_time delta = datetime.timedelta(hours=0) end = start + delta + print(start.isoformat() + "Z") + if haveLength: + # Event with a duration startParams = { "dateTime": start.isoformat(), + "timeZone": get("TIMEZONE", "Etc/GMT"), } endParams = { "dateTime": end.isoformat(), + "timeZone": get("TIMEZONE", "Etc/GMT"), } else: + # Full day event startParams = { "date": start.isoformat()[:10], + "timeZone": get("TIMEZONE", "Etc/GMT"), } endParams = { "date": (end + datetime.timedelta(days=1)).isoformat()[:10], + "timeZone": get("TIMEZONE", "Etc/GMT"), } event_body = { @@ -73,7 +82,13 @@ def create_event(config_dir, calendar, date, summary, details, length, from_time def execute_creation(): LOGGER.debug(calendar, date, summary, details, length, event_body, from_time) - event = service.events().insert(calendarId=calendar, body=event_body).execute() + try: + event = ( + service.events().insert(calendarId=calendar, body=event_body).execute() + ) + except HttpError as err: + LOGGER.error(f"Cannot create the event: {err.status_code}") + raise return event try: From 5ca06a82cec4181f8fba88116d396162cfd5cdce Mon Sep 17 00:00:00 2001 From: Luca Fabbri Date: Sat, 3 Jun 2023 10:56:20 +0200 Subject: [PATCH 3/7] Now only display events respecting USER_EMAIL --- haunts/download.py | 26 ++++++++++++++++++++++++-- haunts/ini.py | 6 +++++- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/haunts/download.py b/haunts/download.py index 1b779d5..bf1b1e3 100644 --- a/haunts/download.py +++ b/haunts/download.py @@ -1,16 +1,36 @@ from googleapiclient.discovery import build from datetime import datetime, timedelta +from .ini import get from .credentials import get_credentials from .spreadsheet import get_calendars from .spreadsheet import SCOPES as SPREADSHEET_SCOPES from .calendars import SCOPES as CALENDAR_SCOPES +def filter_my_event(events): + """ + Take a list of Google Calendar events and returns events created by USER_EMAIL + or events that have USER_EMAIL in the attendees list. + """ + USER_EMAIL = get("USER_EMAIL") + if USER_EMAIL is None: + raise KeyError("USER_EMAIL not set in configuration") + for event in events: + if event.get("creator", {}).get("email") == USER_EMAIL: + yield event + elif USER_EMAIL in [ + attendee.get("email") for attendee in event.get("attendees", []) + ]: + yield event + + def get_events(events_service, calendar_id, date): start_datetime = datetime.combine(date, datetime.min.time()).isoformat() + "Z" end_datetime = ( - datetime.combine(date, datetime.min.time()) + timedelta(days=1) + datetime.combine(date, datetime.min.time()) + + timedelta(days=1) + - timedelta(seconds=1) ).isoformat() + "Z" events_result = events_service.list( calendarId=calendar_id, @@ -18,6 +38,7 @@ def get_events(events_service, calendar_id, date): timeMax=end_datetime, singleEvents=True, orderBy="startTime", + timeZone=get("TIMEZONE", "Etc/GMT"), ).execute() events = events_result.get("items", []) return events @@ -47,9 +68,10 @@ def extract_events(config_dir, sheet): for calendar_id in configued_calendars.values(): print(f"checking {calendar_id}") events = get_events(events_service, calendar_id, date_to_check) - all_events.extend(events) + all_events.extend(filter_my_event(events)) for event in all_events: + print(event) event_summary = event.get("summary", "No summary") start_time = event["start"].get("dateTime", event["start"].get("date")) end_time = event["end"].get("dateTime", event["end"].get("date")) diff --git a/haunts/ini.py b/haunts/ini.py index 88d4abb..3f00186 100644 --- a/haunts/ini.py +++ b/haunts/ini.py @@ -23,8 +23,12 @@ # OVERTIME_FROM=20:00 # User email. -# Required for --execute read +# Required for `--execute read` # USER_EMAIL= + +# Preferred timezone +# Default is GMT +# TIMEZONE=Europe/Rom """ parser = configparser.RawConfigParser(allow_no_value=True) From d7a0709ff719c5e9bf81443b369b5f0f5d484b3c Mon Sep 17 00:00:00 2001 From: Luca Fabbri Date: Sun, 7 Apr 2024 21:34:00 +0200 Subject: [PATCH 4/7] Committing read feature --- README.rst | 37 +++++++++-- haunts/cli.py | 6 +- haunts/download.py | 74 +++++++++++++++------ haunts/spreadsheet.py | 148 ++++++++++++++++++++++++++++++++++++++++-- requirements_dev.txt | 8 +-- setup.py | 7 +- 6 files changed, 238 insertions(+), 42 deletions(-) diff --git a/README.rst b/README.rst index 2acf242..cf54c64 100644 --- a/README.rst +++ b/README.rst @@ -16,7 +16,7 @@ B-Open Haunts What it does ============ -Fill Google Calendars with events taken from a Google Spreadsheet. +Fill Google Calendars with events taken from a Google Spreadsheet. Or the other way around. How to install ============== @@ -90,6 +90,18 @@ To just report overtime entries in the set: haunts --execute report --day=2021-05-24 --day=2021-05-25 --day=2021-05-28 --project="Project X" --overtime May +To *read* today events from all configured calendar and write them on your "May" sheet for the current: + +.. code-block:: bash + + haunts --execute read May + +To *read* events for a specific date from all configured calendar and write them on your "May" sheet for the current: + +.. code-block:: bash + + haunts --execute read -d 2023-05-15 May + How it works ------------ @@ -98,8 +110,14 @@ What haunts does depends on the ``--execute`` parameter. In its default configuration (if ``--execute`` is omitted, or equal to ``sync``), the command will try to access a Google Spreatsheet you must have access to (write access required), specifically: it will read a single sheet at time inside that spreadsheet. Every row inside this sheet is an event that will be also created on a Google Calendar. -Alternatively you can provide ``--execute report``. -In this case it just access the Google Spreadsheet to collect data. +Alternatively you can provide: + +- ``--execute report``. + + In this case it just access the Google Spreadsheet to collect data. +- ``--execute read``. + + In this case it fills the Google Spreadsheet for you, by *reading* you calendars. Sheet definition ---------------- @@ -164,18 +182,23 @@ Every sheet should contains following headers: Configuring projects ~~~~~~~~~~~~~~~~~~~~ -The spreadsheet must also contains a *configuration sheet* (default name is ``config``, can be changed in the .ini) where you must put two columns (with headers): +The spreadsheet must also contains a *configuration sheet* (default name is ``config``, can be changed in the .ini) where you must put at least two columns (with same headers as follows): **id** The id of a Google Calendar associated to this project. You must have write access to this calendar. **name** - The name of the project, like an alias to the calendar + The name of the project, like a human readable name for a calendar. + A project name can be associated to the same calendar id multiple times (this way you can have aliases). + +**read_from** (optional) + User only for ``--execute read``. -A project name can be associated to the same calendar id multiple times. + Read events from this (optional) calendar id instead of the main one. + This makes possible to *read* events from a calendar, but store them in another ones. -Values in the ``name`` column are the only valid values for the ``Project`` column introduced above +Values in the ``name`` column are valid values for the ``Project`` column introduced above. How events will be filled ------------------------- diff --git a/haunts/cli.py b/haunts/cli.py index 20d1683..e55f566 100644 --- a/haunts/cli.py +++ b/haunts/cli.py @@ -146,7 +146,11 @@ def main( elif execute == "report": report(config_dir, sheet, days=day, projects=project, overtime=overtime) elif execute == "read": - extract_events(config_dir, sheet) + extract_events( + config_dir, + sheet, + day=day[0] if day else datetime.date.today().strftime("%Y-%m-%d"), + ) return 0 diff --git a/haunts/download.py b/haunts/download.py index bf1b1e3..ee93c93 100644 --- a/haunts/download.py +++ b/haunts/download.py @@ -1,9 +1,14 @@ from googleapiclient.discovery import build from datetime import datetime, timedelta +import click from .ini import get from .credentials import get_credentials -from .spreadsheet import get_calendars +from .spreadsheet import ( + append_line, + get_calendars_names, + get_calendars, +) from .spreadsheet import SCOPES as SPREADSHEET_SCOPES from .calendars import SCOPES as CALENDAR_SCOPES @@ -25,7 +30,8 @@ def filter_my_event(events): yield event -def get_events(events_service, calendar_id, date): +def get_events_at(events_service, calendar_id, date): + """Get all events from a calendar in a specific date.""" start_datetime = datetime.combine(date, datetime.min.time()).isoformat() + "Z" end_datetime = ( datetime.combine(date, datetime.min.time()) @@ -41,10 +47,15 @@ def get_events(events_service, calendar_id, date): timeZone=get("TIMEZONE", "Etc/GMT"), ).execute() events = events_result.get("items", []) - return events + # Enrich events with calendar_id + return [{**e, "calendar_id": calendar_id} for e in events] -def extract_events(config_dir, sheet): +def extract_events(config_dir, sheet, day): + """Public module entry point. + + Extract events from Google Calendar and copy them to proper Google Sheet. + """ calendar_credentials = get_credentials( config_dir, CALENDAR_SCOPES, "calendars-token.json" ) @@ -54,28 +65,51 @@ def extract_events(config_dir, sheet): calendar_service = build("calendar", "v3", credentials=calendar_credentials) spreadsheet_service = build("sheets", "v4", credentials=spreadsheeet_credentials) - date_to_check = datetime( - year=2023, month=5, day=21 + date_to_check = datetime.strptime( + day, "%Y-%m-%d" ).date() # Replace with the desired date events_service = calendar_service.events() sheet_service = spreadsheet_service.spreadsheets() - configued_calendars = get_calendars(sheet_service, ignore_alias=True) - print(configued_calendars) - + configured_calendars = get_calendars( + sheet_service, ignore_alias=True, use_read_col=True + ) all_events = [] - for calendar_id in configued_calendars.values(): - print(f"checking {calendar_id}") - events = get_events(events_service, calendar_id, date_to_check) - all_events.extend(filter_my_event(events)) + # Get "my events" from all configured calendars in the selected date + already_added_events = set() + for calendar_id in configured_calendars.values(): + events = get_events_at(events_service, calendar_id, date_to_check) + new_events = [ + e for e in filter_my_event(events) if e["id"] not in already_added_events + ] + already_added_events.update([e["id"] for e in new_events]) + all_events.extend(new_events) + + # Get calendar configurations + calendar_names = get_calendars_names(sheet_service) + # Main operation loop for event in all_events: - print(event) event_summary = event.get("summary", "No summary") - start_time = event["start"].get("dateTime", event["start"].get("date")) - end_time = event["end"].get("dateTime", event["end"].get("date")) - print(f"Summary: {event_summary}") - print(f"Start Time: {start_time}") - print(f"End Time: {end_time}") - print("---") + start = event["start"].get("dateTime", event["start"].get("date")) + end = event["end"].get("dateTime", event["end"].get("date")) + project = calendar_names[event["calendar_id"]] + + start_date = datetime.fromisoformat(start).date() + start_time = datetime.fromisoformat(start).time() + duration = datetime.fromisoformat(end) - datetime.fromisoformat(start) + click.echo(f"Adding new event {event_summary} ({project}) to selected sheet") + append_line( + sheet_service, + sheet, + date_col=start_date, + time_col=start_time, + duration_col=duration, + project_col=project, + activity_col=event_summary, + details_col=event.get("description", ""), + event_id_col=event["id"], + link_col=event.get("htmlLink", ""), + action_col="I", + ) diff --git a/haunts/spreadsheet.py b/haunts/spreadsheet.py index 3d48d63..600b07d 100644 --- a/haunts/spreadsheet.py +++ b/haunts/spreadsheet.py @@ -231,9 +231,12 @@ def sync_events( ) -def get_calendars(sheet, ignore_alias=False): - """In case ignore_alias is true, only the first occurence of a calendar is returned.""" - RANGE = f"{get('CONTROLLER_SHEET_NAME', 'config')}!A2:B" +def get_calendars(sheet, ignore_alias=False, use_read_col=False): + """In case ignore_alias is true, only the first occurence of a calendar is returned. + + In case use_read_col is True, the preferred calendar id is taken from "read_from" column + """ + RANGE = f"{get('CONTROLLER_SHEET_NAME', 'config')}!A2:C" calendars = ( sheet.values() .get(spreadsheetId=get("CONTROLLER_SHEET_DOCUMENT_ID"), range=RANGE) @@ -241,13 +244,146 @@ def get_calendars(sheet, ignore_alias=False): ) values = calendars.get("values", []) configured_calendars = {} - for id, alias in values: - if ignore_alias and id in configured_calendars: + for cols in values: + if not use_read_col: + id, alias = cols + read_from = None + else: + try: + id, alias, read_from = cols + except ValueError: + # no linked_id + id, alias = cols + read_from = None + if ignore_alias and ( + id in configured_calendars or read_from in configured_calendars + ): continue - configured_calendars[alias] = id + configured_calendars[alias] = read_from or id return configured_calendars +def get_calendars_names(sheet): + """Get all calendars names, giving precedence to alias defined in column "linked_calendar". + + If aliases are found, the first one will be used + """ + RANGE = f"{get('CONTROLLER_SHEET_NAME', 'config')}!A2:C" + calendars = ( + sheet.values() + .get(spreadsheetId=get("CONTROLLER_SHEET_DOCUMENT_ID"), range=RANGE) + .execute() + ) + values = calendars.get("values", []) + names = {} + for cols in values: + try: + id, alias, linked_id = cols + except ValueError: + # no linked_id + id, alias = cols + linked_id = None + if names.get(linked_id) or (names.get(id) and not linked_id): + continue + names[linked_id or id] = alias + return names + + +def get_first_empty_line(sheet, month): + """Get the first empty line in a month.""" + RANGE = f"{month}!A1:A" + lines = ( + sheet.values() + .get(spreadsheetId=get("CONTROLLER_SHEET_DOCUMENT_ID"), range=RANGE) + .execute() + ) + values = lines.get("values", []) + return len(values) + 1 + + +def format_duration(duration): + """Given a timedelta duration, format is as a string. + + String format will be H,X or H if minutes are 0. + X is the decimal part of the hour (30 minutes are 0.5 hours, etc) + """ + hours = duration.total_seconds() / 3600 + if hours % 1 == 0: + return str(int(hours)) + return str(hours).replace(".", ",") + + +def append_line( + sheet, + month, + date_col, + time_col, + project_col, + activity_col, + event_id_col=None, + link_col="", + details_col="", + action_col="", + duration_col=None, +): + """Append a new line at the end of a sheet.""" + next_av_line = get_first_empty_line(sheet, month) + headers_id = get_headers(sheet, month, indexes=True) + # Now write a new line at position next_av_line + RANGE = f"{month}!A{next_av_line}:ZZ{next_av_line}" + values_line = [] + formatted_time_col = time_col.strftime("%H:%M") if time_col else "" + formatted_duration_col = format_duration(duration_col) if duration_col else "" + full_day = formatted_time_col == "00:00" and formatted_duration_col == "24" + for key, index in headers_id.items(): + if key == "Date": + values_line.append(date_col.strftime("%d/%m/%Y")) + elif key == "Start time": + values_line.append(formatted_time_col if not full_day else "") + elif key == "Project": + values_line.append(project_col) + elif key == "Activity": + values_line.append(activity_col) + elif key == "Details": + values_line.append(details_col) + elif key == "Event id": + values_line.append(event_id_col) + elif key == "Link": + values_line.append(link_col) + elif key == "Action": + values_line.append(action_col) + elif key == "Spent": + values_line.append(formatted_duration_col if not full_day else "") + else: + values_line.append("") + + request = sheet.values().batchUpdate( + spreadsheetId=get("CONTROLLER_SHEET_DOCUMENT_ID"), + body={ + "valueInputOption": "USER_ENTERED", + "data": [ + { + "range": RANGE, + "values": [values_line], + }, + ], + }, + ) + + try: + request.execute() + except HttpError as err: + if err.status_code == 429: + click.echo("Too many requests") + click.echo(err.error_details) + click.echo("haunts will now pause for a while ⏲…") + time.sleep(60) + click.echo("Retrying…") + request.execute() + else: + raise + + def sync_report(config_dir, month, days=[], projects=[], allowed_actions=[]): """Open a sheet, analyze it and populate calendars with new events.""" # The ID and range of the controller timesheet diff --git a/requirements_dev.txt b/requirements_dev.txt index fd9cc21..ec63cc8 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -1,4 +1,4 @@ -twine==4.0.1 -click==8.1.3 -pre-commit==2.20.0 -ruff=0.0.167 \ No newline at end of file +twine==4.0.2 +click==8.1.7 +pre-commit==3.7.0 +ruff==0.3.5 \ No newline at end of file diff --git a/setup.py b/setup.py index 28beab9..ec44790 100644 --- a/setup.py +++ b/setup.py @@ -25,16 +25,15 @@ setup( author="Luca Fabbri", author_email="l.fabbri@bopen.eu", - python_requires=">=3.8", + python_requires=">=3.9", classifiers=[ "Development Status :: 4 - Beta", "Intended Audience :: End Users/Desktop", "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", "Natural Language :: English", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", ], description="Fill and sync Google Calendars with events taken from a Google spreadsheet", entry_points={ @@ -46,7 +45,7 @@ license="GNU General Public License v3", long_description=readme + "\n\n" + history, include_package_data=True, - keywords="google-calendar spreadsheet reports", + keywords="google-calendar spreadsheet reports worklog", name="haunts", packages=find_packages(include=["haunts", "haunts.*"]), test_suite="tests", From 70e34b460cf8a5447519349d9841b81e1c9a5345 Mon Sep 17 00:00:00 2001 From: Luca Fabbri Date: Tue, 9 Apr 2024 18:35:55 +0200 Subject: [PATCH 5/7] Updated history --- HISTORY.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HISTORY.rst b/HISTORY.rst index a6298e3..b49ced6 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -4,7 +4,7 @@ History 0.6.1 (unreleased) ------------------ -- Nothing changed yet. +- Added ``read`` option to ``--execute`` 0.6.0 (2023-03-31) From 97b94b78a3c810504b643873a9ff035e593e6eb5 Mon Sep 17 00:00:00 2001 From: Luca Fabbri Date: Tue, 9 Apr 2024 18:36:08 +0200 Subject: [PATCH 6/7] Preparing release 0.7.0 --- HISTORY.rst | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index b49ced6..e365e55 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -1,7 +1,7 @@ History ======= -0.6.1 (unreleased) +0.7.0 (2024-04-09) ------------------ - Added ``read`` option to ``--execute`` diff --git a/setup.py b/setup.py index ec44790..44e2180 100644 --- a/setup.py +++ b/setup.py @@ -51,6 +51,6 @@ test_suite="tests", tests_require=test_requirements, url="https://github.com/keul/haunts", - version="0.6.1.dev0", + version="0.7.0", zip_safe=False, ) From 569849f951dbbf9d5291ebe6ab5591f7dc07427e Mon Sep 17 00:00:00 2001 From: Luca Fabbri Date: Tue, 9 Apr 2024 18:36:22 +0200 Subject: [PATCH 7/7] Back to development: 0.7.1 --- HISTORY.rst | 6 ++++++ setup.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/HISTORY.rst b/HISTORY.rst index e365e55..5df525b 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -1,6 +1,12 @@ History ======= +0.7.1 (unreleased) +------------------ + +- Nothing changed yet. + + 0.7.0 (2024-04-09) ------------------ diff --git a/setup.py b/setup.py index 44e2180..6ae96d9 100644 --- a/setup.py +++ b/setup.py @@ -51,6 +51,6 @@ test_suite="tests", tests_require=test_requirements, url="https://github.com/keul/haunts", - version="0.7.0", + version="0.7.1.dev0", zip_safe=False, )