Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue 481 pyproject requirements #643

Merged
merged 6 commits into from
Feb 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@
!/open_web_calendar
/open_web_calendar/test
/open_web_calendar/features
!/requirements.txt
!/requirements
!/docker
!/LICENSE
!/LICENSES
!/REUSE.toml
!/pyproject.toml
19 changes: 10 additions & 9 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@
FROM python:3.13-alpine

# make pip also use piwheels
ADD docker/pip.conf /etc/pip.conf
COPY docker/pip.conf /etc/pip.conf

# licenses
ADD LICENSE .
ADD LICENSES .
ADD REUSE.toml .
COPY LICENSE .
COPY LICENSES .
COPY REUSE.toml .

# server environment variables
EXPOSE 80
Expand All @@ -21,13 +21,14 @@ ENV WORKERS=4
RUN mkdir /app
WORKDIR /app
ENV PYTHONUNBUFFERED=true
ADD docker/start-service.sh .
COPY docker/start-service.sh .

# Install Packages
ADD docker/constraints.txt .
COPY docker/constraints.txt .
ENV PIP_CONSTRAINT=constraints.txt
ADD requirements.txt .
RUN apk add libxslt libxml2 libxslt-dev libxml2-dev gcc libc-dev \
COPY requirements/base.txt requirements.txt
COPY pyproject.toml .
RUN apk add --no-cache gcc libc-dev libxml2 libxml2-dev libxslt libxslt-dev \
&& pip install --upgrade --no-cache-dir pip \
&& pip install --upgrade --no-cache-dir -r requirements.txt \
&& apk del libxslt-dev libxml2-dev gcc libc-dev
Expand All @@ -36,4 +37,4 @@ RUN apk add libxslt libxml2 libxslt-dev libxml2-dev gcc libc-dev \
ENTRYPOINT ["/bin/sh", "start-service.sh"]

# Add the app
ADD open_web_calendar open_web_calendar
COPY open_web_calendar open_web_calendar
2 changes: 1 addition & 1 deletion REUSE.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ SPDX-FileCopyrightText = "2024 Nicco Kunzmann and Open Web Calendar Contributors
SPDX-License-Identifier = "GPL-2.0-only"

[[annotations]]
path = ["test-requirements.**", "requirements.**", "docs-requirements.**"]
path = ["requirements/**"]
precedence = "aggregate"
SPDX-FileCopyrightText = "2024 Nicco Kunzmann and Open Web Calendar Contributors <https://open-web-calendar.quelltext.eu/>"
SPDX-License-Identifier = "GPL-2.0-only"
Expand Down
3 changes: 3 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ The latest version might not be released, yet.
## v1.46

- Fix escaping [JavaScript](https://github.com/niccokunzmann/open-web-calendar/issues/631) and [CSS](https://github.com/niccokunzmann/open-web-calendar/issues/396)
- Allow installation with fixed depdencies using `pip install open-web-calendar[production]`
- Update dependencies
- Use `pip-compile-multi` and `hatch-requirements-txt` for dependencies, see [Issue 481](https://github.com/niccokunzmann/open-web-calendar/issues/481).

## v1.45

Expand Down
34 changes: 9 additions & 25 deletions docs/dev/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,37 +38,14 @@ tox -e ruff
[ruff]: https://docs.astral.sh/ruff/
[pre-commit]: https://pre-commit.com/

## Running the App

1. Optional: Install virtualenv and Python3 and create a virtual environment.

```sh
virtualenv -p python3 ENV
source ENV/bin/activate
```

2. Install the packages.

```sh
pip install -r requirements.txt
```

3. Start the app.

```sh
python3 app.py
```

You can [configure the app](../../host/configure) through environment variables.

## Running Tests

To run the tests, we use `tox`.
`tox` tests all different Python versions which we want to
be compatible to.

```sh
pip install tox
pip install -r requirements/dev.in
```

Run all tests:
Expand Down Expand Up @@ -106,6 +83,14 @@ You can also change the layout of the window to test the responsive design:
tox -e web -- -D window=375x812 # iPhone11 size
```

### Debug Mode

In case the browser tests fail, screenshots are recorded and a link is printed.

```sh

```

## Documentation

You can build the documentation with `tox`, too.
Expand All @@ -118,5 +103,4 @@ tox -e docs -- serve

We are using [mkdocs] with the [material theme](https://squidfunk.github.io/mkdocs-material/).

[web]: https://open-web-calendar.hosted.quelltext.eu/
[mkdocs]: https://www.mkdocs.org
35 changes: 4 additions & 31 deletions docs/dev/maintain.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,38 +12,11 @@ This section clarifies how to maintain the project.

## Update Dependencies

1. Enter your virtual environment. E.g.
To update the dependencies, run this command:

```sh
source .tox/py311/bin/activate
```

2. Install all dependencies:

```sh
pip install --upgrade -r requirements.in -r test-requirements.in -r docs-requirements.in pip-tools
```

3. Fix the dependencies:

```sh
rm *requirements.txt
pip-compile --output-file=requirements.txt requirements.in
pip-compile --output-file=test-requirements.txt test-requirements.in
pip-compile --output-file=docs-requirements.txt docs-requirements.in
```

4. Create a branch, commit:

```sh
git branch -d update
git checkout -b update
git add *requirements.txt
git commit -m"Update dependencies"
git push -u origin update
```

5. Create a Pull Request and see if the tests run.
```sh
pip-compile-multi --upgrade
```

## Update DHTMLX Scheduler

Expand Down
25 changes: 22 additions & 3 deletions docs/host/pypi.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,26 +11,33 @@ description: "The Open Web Calendar can be installed as a Python package from Py
![](https://img.shields.io/pypi/v/open-web-calendar.svg) ![](https://img.shields.io/pypi/pyversions/open-web-calendar.svg)

## Installation

The Open Web Calendar is available as a Python package on [PyPI]({{link.pypi}}).
If you have [Python 3](https://www.python.org/) installed, run this to install the package:

```shell
pip install open-web-calendar
pip install open-web-calendar[production]
```

At this point you might get the error message

```shell
error: externally-managed-environment
```

On your productive system, it is strongly recommended to follow the instructions to create a virtual environment using

```shell
python3 -m .venv
source .venv/bin/activate
```
followed by

followed by

```shell
pip install open-web-calendar
pip install open-web-calendar[production]
```

This will install your open-web-calendar app in the project folder
/home/username/venv/lib/python3.12/site-packages/open_web_calendar

Expand All @@ -49,15 +56,19 @@ In this case you might want to start the open_web_calendar with the dedicated IP
```shell
gunicorn -b 0.0.0.0 open_web_calendar:app
```

You should now see the server running at [http://192.168.178.7:8000](http://192.168.178.7:8000) not only from your local machine.

## Automatic Startup

In order to start this service automatically at startup, you are required to create a systemd service script.

```shell
sudo nano /etc/systemd/system/open_web_calendar.service
```

Copy the following text into that file and do not forget to change **username** and **usergroup** accordingly!

```shell
[Unit]
Description=Gunicorn instance for the open_web_calendar
Expand All @@ -78,20 +89,28 @@ PrivateTmp=true
[Install]
WantedBy=multi-user.target
```

Finish the installation by
a) Reload and enable the systemd manager

```shell
sudo systemctl daemon-reload
```

b) Enable the service to start on boot

```shell
sudo systemctl enable open_web_calendar
```

c) Start the service:

```shell
sudo systemctl start open_web_calendar
```

d) Verify that the service is running without errors

```shell
sudo systemctl status open_web_calendar
```
Expand Down
8 changes: 4 additions & 4 deletions open_web_calendar/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -279,10 +279,10 @@ def get_calendar(ext):
if ext == "html":
template_name = specification["template"]
all_template_names = os.listdir(CALENDAR_TEMPLATE_FOLDER)
assert (
template_name in all_template_names
), 'Template names must be file names like "{}", not "{}".'.format(
'", "'.join(all_template_names), template_name
assert template_name in all_template_names, (
'Template names must be file names like "{}", not "{}".'.format(
'", "'.join(all_template_names), template_name
)
)
template = CALENDARS_TEMPLATE_FOLDER_NAME + "/" + template_name
return render_app_template(template, specification)
Expand Down
60 changes: 30 additions & 30 deletions open_web_calendar/features/steps/browser_steps.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,19 +111,19 @@ def step_impl(context, count=1):
events = context.browser.find_elements(
By.XPATH, "//div[contains(@class, ' event ')]"
)
assert (
len(events) == count
), f"Expected {count} events but {len(events)} were found: {events!r}"
assert len(events) == count, (
f"Expected {count} events but {len(events)} were found: {events!r}"
)


@then('we see that event "{uid}" has the text "{text}"')
def step_impl(context, uid, text):
events = context.browser.find_elements(
By.XPATH, f"//div[contains(@event_id, {uid!r})]"
)
assert (
len(events) == 1
), f"There should only be one event with UID {uid} but there are {len(events)}."
assert len(events) == 1, (
f"There should only be one event with UID {uid} but there are {len(events)}."
)
event = events[0]
inner_text = re.sub(r"\s+", " ", event.get_attribute("innerText"))
assert inner_text == text, f"Expected {text!r} but got {inner_text!r}"
Expand Down Expand Up @@ -163,9 +163,9 @@ def step_impl(context, text):
@when('we click the link "{text}"')
def step_impl(context, text):
links = context.browser.find_elements(By.XPATH, f"//a[text() = {text!r}]")
assert (
len(links) == 1
), f"I should click on the link {text!r} but found {len(links)}."
assert len(links) == 1, (
f"I should click on the link {text!r} but found {len(links)}."
)
links[0].click()


Expand Down Expand Up @@ -197,23 +197,23 @@ def step_impl(context, text):
index = body.find(text)
start = 0 if index < 10 else index - 10
end = -1 if index > len(body) else index + 10
assert (
index == -1
), f"{text!r} is visible but should not be visible: {body[start:end]!r}"
assert index == -1, (
f"{text!r} is visible but should not be visible: {body[start:end]!r}"
)


@then('we can see the text "{text}"')
def step_impl(context, text):
assert text in get_body_text(
context
), f"{text!r} is invisible but should be visible"
assert text in get_body_text(context), (
f"{text!r} is invisible but should be visible"
)


@then("we can see a {cls}")
def step_impl(context, cls):
assert context.browser.find_elements(
By.CLASS_NAME, cls
), f"Expected to find elements of class {cls}"
assert context.browser.find_elements(By.CLASS_NAME, cls), (
f"Expected to find elements of class {cls}"
)


@then('we can see an event with UID "{uid}" with css class "{css_class}"')
Expand All @@ -231,9 +231,9 @@ def step_impl(context, uid, css_class):
@then("we cannot see a {cls}")
def step_impl(context, cls):
with no_time_to_wait_for_elements(context):
assert not context.browser.find_elements(
By.CLASS_NAME, cls
), f"Expected to not find elements of class {cls}"
assert not context.browser.find_elements(By.CLASS_NAME, cls), (
f"Expected to not find elements of class {cls}"
)


@when("we open the about page")
Expand Down Expand Up @@ -305,9 +305,9 @@ def step_impl(context, text, field_id):
"""Check that a field has a value."""
input_element = context.browser.find_element(By.ID, field_id)
actual_text = input_element.get_attribute("value")
assert (
actual_text == text
), f"Expected {text!r} in {field_id} but got {actual_text!r}."
assert actual_text == text, (
f"Expected {text!r} in {field_id} but got {actual_text!r}."
)


@when('we write the date {day}/{month}/{year} into "{field_id}"')
Expand Down Expand Up @@ -365,9 +365,9 @@ def assert_specification_has_value(context, attribute, expected_value="no value"
"""Make sure the specification has a certain value."""
specification = get_specification(context)
actual_value = specification.get(attribute, "no value")
assert (
actual_value == expected_value
), f"specification.{attribute}: expected {expected_value} but got {actual_value}."
assert actual_value == expected_value, (
f"specification.{attribute}: expected {expected_value} but got {actual_value}."
)


@then('"{attribute}" is specified as {expected_value}')
Expand Down Expand Up @@ -453,9 +453,9 @@ def assert_tag_with_text_attribute_equals(
elements = context.browser.find_elements(
By.XPATH, f"//{tag}[text()[contains(., {text!r})]]"
)
assert (
len(elements) >= 1
), f"Expected at least one <{tag}> with text {text!r} but got {len(elements)}."
assert len(elements) >= 1, (
f"Expected at least one <{tag}> with text {text!r} but got {len(elements)}."
)
actual_values = [element.get_attribute(attribute) for element in elements]
assert expected_value in actual_values, (
f"Expected a <{tag}> with the text {text!r} to have an attribute "
Expand Down
Loading