diff --git a/CHANGES.rst b/CHANGES.rst index 62880976..90cf1c9b 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -63,6 +63,7 @@ New features: - icalendar utility outputs a 'Duration' row - icalendar can take multiple ics files as an input +- source code in documentation is tested using doctest #445 [niccokunzmann] Bug fixes: diff --git a/docs/install.rst b/docs/install.rst index eb1afb14..cc938c53 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -112,7 +112,7 @@ Try it out: Type "help", "copyright", "credits" or "license" for more information. >>> import icalendar >>> icalendar.__version__ - '4.0.10.dev0' + '5.0.1' Building the documentation locally ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/maintenance.rst b/docs/maintenance.rst index 2313ac43..6fb7460e 100644 --- a/docs/maintenance.rst +++ b/docs/maintenance.rst @@ -56,7 +56,10 @@ However, only people with ``PyPI environment access for GitHub Actions`` can app 1. Check that the ``CHANGES.rst`` is up to date with the `latest merged pull requests `__ and the version you want to release is correctly named. -2. Change the ``__version__`` variable in the ``src/icalendar/__init__.py`` file. +2. Change the ``__version__`` variable in + + - the ``src/icalendar/__init__.py`` file and + - in the ``docs/usage.rst`` file (look for ``icalendar.__version__``) 3. Create a commit on the ``release-5.0.0`` branch (or equivalent) to release this version. .. code-block:: bash diff --git a/docs/usage.rst b/docs/usage.rst index 48b6e9b7..63786714 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -79,8 +79,8 @@ you do it like this. The calendar is a component:: >>> cal['summary'] = 'Python meeting about calendaring' >>> for k,v in cal.items(): ... k,v - (u'DTSTART', '20050404T080000') - (u'SUMMARY', 'Python meeting about calendaring') + ('DTSTART', '20050404T080000') + ('SUMMARY', 'Python meeting about calendaring') NOTE: the recommended way to add components to the calendar is to use create the subcomponent and add it via Calendar.add! The example above adds a @@ -90,7 +90,7 @@ string, but not a vText component. You can generate a string for a file with the to_ical() method:: >>> cal.to_ical() - 'BEGIN:VCALENDAR\r\nDTSTART:20050404T080000\r\nSUMMARY:Python meeting about calendaring\r\nEND:VCALENDAR\r\n' + b'BEGIN:VCALENDAR\r\nDTSTART:20050404T080000\r\nSUMMARY:Python meeting about calendaring\r\nEND:VCALENDAR\r\n' The rendered view is easier to read:: @@ -102,13 +102,13 @@ The rendered view is easier to read:: So, let's define a function so we can easily display to_ical() output:: >>> def display(cal): - ... return cal.to_ical().decode("utf-8").replace(b'\r\n', b'\n').strip() + ... return cal.to_ical().decode("utf-8").replace('\r\n', '\n').strip() You can set multiple properties like this:: >>> cal = Calendar() >>> cal['attendee'] = ['MAILTO:maxm@mxm.dk','MAILTO:test@example.com'] - >>> print display(cal) + >>> print(display(cal)) BEGIN:VCALENDAR ATTENDEE:MAILTO:maxm@mxm.dk ATTENDEE:MAILTO:test@example.com @@ -122,7 +122,7 @@ added. Here is an example:: >>> cal = Calendar() >>> cal.add('attendee', 'MAILTO:maxm@mxm.dk') >>> cal.add('attendee', 'MAILTO:test@example.com') - >>> print display(cal) + >>> print(display(cal)) BEGIN:VCALENDAR ATTENDEE:MAILTO:maxm@mxm.dk ATTENDEE:MAILTO:test@example.com @@ -148,7 +148,7 @@ component:: And then appending it to a "parent":: >>> cal.add_component(event) - >>> print display(cal) + >>> print(display(cal)) BEGIN:VCALENDAR ATTENDEE:MAILTO:maxm@mxm.dk ATTENDEE:MAILTO:test@example.com @@ -161,7 +161,7 @@ And then appending it to a "parent":: Subcomponents are appended to the subcomponents property on the component:: >>> cal.subcomponents - [VEVENT({'DTSTART': '20050404T080000', 'UID': '42'})] + [VEVENT({'UID': '42', 'DTSTART': '20050404T080000'})] Value types @@ -184,7 +184,7 @@ type defined in the spec:: >>> from datetime import datetime >>> cal.add('dtstart', datetime(2005,4,4,8,0,0)) >>> cal['dtstart'].to_ical() - '20050404T080000' + b'20050404T080000' If that doesn't work satisfactorily for some reason, you can also do it manually. @@ -197,7 +197,7 @@ So if you want to do it manually:: >>> from icalendar import vDatetime >>> now = datetime(2005,4,4,8,0,0) >>> vDatetime(now).to_ical() - '20050404T080000' + b'20050404T080000' So the drill is to initialise the object with a python built in type, and then call the "to_ical()" method on the object. That will return an @@ -220,7 +220,7 @@ value directly:: >>> cal = Calendar() >>> cal.add('dtstart', datetime(2005,4,4,8,0,0)) >>> cal['dtstart'].to_ical() - '20050404T080000' + b'20050404T080000' >>> cal.decoded('dtstart') datetime.datetime(2005, 4, 4, 8, 0) @@ -232,12 +232,13 @@ Property parameters are automatically added, depending on the input value. For example, for date/time related properties, the value type and timezone identifier (if applicable) are automatically added here:: + >>> import pytz >>> event = Event() >>> event.add('dtstart', datetime(2010, 10, 10, 10, 0, 0, ... tzinfo=pytz.timezone("Europe/Vienna"))) >>> lines = event.to_ical().splitlines() - >>> self.assertTrue( + >>> assert ( ... b"DTSTART;TZID=Europe/Vienna;VALUE=DATE-TIME:20101010T100000" ... in lines) @@ -247,9 +248,9 @@ dictionary to the add method like so:: >>> event = Event() >>> event.add('X-TEST-PROP', 'tryout.', - .... parameters={'prop1': 'val1', 'prop2': 'val2'}) + ... parameters={'prop1':'val1', 'prop2':'val2'}) >>> lines = event.to_ical().splitlines() - >>> self.assertTrue(b"X-TEST-PROP;PROP1=val1;PROP2=val2:tryout." in lines) + >>> assert b"X-TEST-PROP;PROP1=val1;PROP2=val2:tryout." in lines Example @@ -270,7 +271,6 @@ Some properties are required to be compliant:: We need at least one subcomponent for a calendar to be compliant:: - >>> import pytz >>> event = Event() >>> event.add('summary', 'Python meeting about calendaring') >>> event.add('dtstart', datetime(2005,4,4,8,0,0,tzinfo=pytz.utc)) @@ -314,6 +314,7 @@ Write to disk:: >>> directory = tempfile.mkdtemp() >>> f = open(os.path.join(directory, 'example.ics'), 'wb') >>> f.write(cal.to_ical()) + 570 >>> f.close() diff --git a/src/icalendar/tests/test_with_doctest.py b/src/icalendar/tests/test_with_doctest.py new file mode 100644 index 00000000..652198c3 --- /dev/null +++ b/src/icalendar/tests/test_with_doctest.py @@ -0,0 +1,67 @@ +"""This file tests the source code provided by the documentation. + +See +- doctest documentation: https://docs.python.org/3/library/doctest.html +- Issue 443: https://github.com/collective/icalendar/issues/443 + +This file should be tests, too: + + >>> print("Hello World!") + Hello World! + +""" +import doctest +import os +import pytest +import importlib + +HERE = os.path.dirname(__file__) or "." +ICALENDAR_PATH = os.path.dirname(HERE) + +PYTHON_FILES = [ + os.path.join(dirpath, filename) + for dirpath, dirnames, filenames in os.walk(ICALENDAR_PATH) + for filename in filenames if filename.lower().endswith(".py") +] + +MODULE_NAMES = [ + "icalendar" + python_file[len(ICALENDAR_PATH):-3].replace("/", ".") + for python_file in PYTHON_FILES +] + +def test_this_module_is_among_them(): + assert __name__ in MODULE_NAMES + +@pytest.mark.parametrize("module_name", MODULE_NAMES) +def test_docstring_of_python_file(module_name): + """This test runs doctest on the Python module.""" + module = importlib.import_module(module_name) + test_result = doctest.testmod(module, name=module_name) + assert test_result.failed == 0, f"{test_result.failed} errors in {module_name}" + + +# This collection needs to exclude .tox and other subdirectories +DOCUMENTATION_PATH = os.path.join(HERE, "../../../") + +DOCUMENT_PATHS = [ + os.path.join(DOCUMENTATION_PATH, subdir, filename) + for subdir in ["docs", "."] + for filename in os.listdir(os.path.join(DOCUMENTATION_PATH, subdir)) + if filename.lower().endswith(".rst") +] + +@pytest.mark.parametrize("filename", [ + "README.rst", + "index.rst", +]) +def test_files_is_included(filename): + assert any(path.endswith(filename) for path in DOCUMENT_PATHS) + +@pytest.mark.parametrize("document", DOCUMENT_PATHS) +def test_documentation_file(document): + """This test runs doctest on a documentation file.""" + test_result = doctest.testfile(document, module_relative=False) + assert test_result.failed == 0, f"{test_result.failed} errors in {os.path.basename(document)}" + + + diff --git a/tox.ini b/tox.ini index 30a56afc..3a13fd5c 100644 --- a/tox.ini +++ b/tox.ini @@ -10,6 +10,7 @@ usedevelop=True deps = pytest coverage + hypothesis commands = coverage run --source=src/icalendar --omit=*/tests/* --module pytest [] coverage report