This is @honzajavorek's experimental playground for the next generation of the python.cz website.
-
The honzajavorek/py repository is not a direct fork of pyvec/python.cz. It has a common git commit history with it, but several large directories have been nuked:
talks
files
identity
pythoncz/static/talks
pythoncz/static/photos
-
The project uses CircleCI as it has a lot of features, supports fanning out individual builders, supports crontab syntax in the config, and runs the builds at once without waiting.
-
The project uses Now for deployments as it supports redirects and various other features. There is a custom deployment script in the CLI to pioneer the best way how to automatically deploy to Now - see pyvec/elsa#61.
-
The project structure is broken down to separate page builders and the website, with the intention to keep maintainable testability, clear interfaces, and to have the simplest code possible. With such separation it's also easy to develop the site locally with only a certain part of the site backed by real data. (Addressing #279)
-
The main purpose of the python.cz website is now defined as an aggregator. It should be a place where all the diversity of the Czech Python community's activities federates and where individual projects can be discovered:
- python.cz doesn't contain job posts, it aggregates job boards
- python.cz doesn't contain events, it aggregates events
- python.cz doesn't contain study materials, it links to them
- python.cz doesn't contain Czech articles about Python, it aggregates them
build
- a directory with the static HTML output intended to get deployedpythoncz
- a root Python package of the projectcli
- the CLI implementationpages
- individual python.cz pagesstatic
- static filestemplates
- templates for the page views__init__.py
- where the Flask app is instantiated and configured__main__.py
- where the CLI is instantiateddata.py
- utilities to save and load static data for pages in a uniform way
Each package in the pages
directory is responsible for a single page of the python.cz website. A page is a logical concept. It can include multiple routes as far as their content is backed by the same data. As an example, the events
page takes care of following routes:
/events/
/events.ics
/events.json
A minimal page can be as simple as having only the views.py
file with Flask routes (see the index
page). A complex, data-backed page has several mandatory parts:
__main__.py
- Page builder, which can be ran separately as a script (pipenv run build events
), produces static, serializable data as an output (data_events.json
), and usespythoncz.data.save_data()
to save them. Builder should contain all dirty side effects: network, filesystem, etc. It stays untested.views.py
- Routes, which usepythoncz.data.load_data()
to compose their context for the templates, and which are allowed to contain only presentational logic. They stay untested.__init__.py
- A library of pure functions, which are used by the page builder or the routes to do their job. These functions are supposed to be easy to understand and should be 100% tested.test_*.py
ortests/test_*.py
- Tests for the library functions*_data.json
- Throwaway files of serialized data. Product of the page builders, input for the routes. If they're not present,pythoncz.data.load_data()
only warns and allows the routes to render with empty data.
When adding new pages, don't forget to import their views at the bottom of the pythoncz/__init__.py
file. Also don't forget to add the page builder to the CI configuration.
Such structure decouples the process of getting the data from their presentation on the website. It also decouples hard-to-test side effects from the pure testable core, where the business logic is. With the library functions tested by automated unit tests, it's not such a big deal to rely on manual testing of the page builder and visual testing of the website.
The architecture is inspired by:
What if there's a page which needs data from a different page? If it's only for presentational purposes (index
displaying latest articles and events), it can pythoncz.data.load_data()
from various places, no problem. The path to the data file is explicit and not bound to the package where the calling view is.
If the page builder could benefit from seeing other page's data file, then it should restrain from doing so. It's very unlikely two pages will need the very same data. Even if they ask from the same sources, they probably still need different data. Dependenices between page builders ruin the whole thing. With them, you need to pay attention to order of the builders, you cannot run them in parallel on CI, etc. I'd say little redundancy in corner cases isn't bad if it allows for decoupling things. Just let every builder to request their sources and restrain them from looking over the fence to their neighbor's data.
There is no tests
directory. Tests stay with the code to support their discoverability and maintainability of clear boundaries between individual parts of the code.
If one tests file is enough, call it test_*.py
and put it alongside your code. If you need fixtures or more files, create an ad-hoc tests
directory and put the test_*.py
files and fixtures inside.
Thanks to the fact only the pure, library functions are (unit) tested, the test suite should be very fast.
pipenv run test
- runs the test suitepipenv run serve
- dynamically serves the Flask websitepipenv run build
- builds all pages and the static websitepipenv run build events
- builds only data for the 'events' pagepipenv run build web
- builds only the static websitepipenv run deploy
- deploys contents of thebuild
directory to Now
- Think about what data you're going to need in your views.
- Prototype a builder for your page. Run it with
pipenv run build <name>
and see what it does. Generate a data file and see what's in it. Do not clutter the data file with anything which isn't valuable for the views! If it's ever needed in the future, somebody will add it. - Clean up the builder code to contain only dirty, shell-like, I/O-heavy stuff, and put everything else into pure functions with clear arguments and return values. Put the functions into
__init__.py
and test them. - Add views. If they need to reshuffle the data to display them, or to do anything else with them before returning the HTTP response, and it won't fit into one or two lines, extract it as a pure function in
__init__.py
and test it. Your views should be as slim as possible, taking care exclusively of the HTTP mechanics and presentational response rendering. - Check whether
pipenv run build <name>
does what you want, whetherpipenv run serve
displays what you want, whetherpipenv run test
passes. If it does, you're done!
If anything good comes out of the development, @honzajavorek will donate the code to the @pyvec organization and we can have community talks on whether we want to switch the source for the python.cz domain. As this isn't a clean fork, there will never be a Pull Request back to pyvec/python.cz.