mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-07-09 03:04:54 -04:00
* parses datetime properly + clean category - #248 * add default credentials to docs Co-authored-by: hay-kot <hay-kot@pm.me>
This commit is contained in:
parent
6e15a8c439
commit
1379529df5
@ -23,6 +23,11 @@ docker run \
|
|||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
!!! tip "Default Credentials"
|
||||||
|
**Username:** changeme@email.com
|
||||||
|
|
||||||
|
**Password:** MyPassword
|
||||||
|
|
||||||
## Docker Compose with SQLite
|
## Docker Compose with SQLite
|
||||||
Deployment with docker-compose is the recommended method for deployment. The example below will create an instance of mealie available on port `9925` with the data volume mounted from the local directory. To use, create a docker-compose.yml file, paste the contents below and save. In the terminal run `docker-compose up -d` to start the container.
|
Deployment with docker-compose is the recommended method for deployment. The example below will create an instance of mealie available on port `9925` with the data volume mounted from the local directory. To use, create a docker-compose.yml file, paste the contents below and save. In the terminal run `docker-compose up -d` to start the container.
|
||||||
|
|
||||||
|
File diff suppressed because one or more lines are too long
@ -1,6 +1,6 @@
|
|||||||
import html
|
import html
|
||||||
import re
|
import re
|
||||||
from datetime import datetime
|
from datetime import datetime, timedelta
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
from slugify import slugify
|
from slugify import slugify
|
||||||
@ -10,8 +10,6 @@ class Cleaner:
|
|||||||
"""A Namespace for utility function to clean recipe data extracted
|
"""A Namespace for utility function to clean recipe data extracted
|
||||||
from a url and returns a dictionary that is ready for import into
|
from a url and returns a dictionary that is ready for import into
|
||||||
the database. Cleaner.clean is the main entrypoint
|
the database. Cleaner.clean is the main entrypoint
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -44,7 +42,7 @@ class Cleaner:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def category(category: str):
|
def category(category: str):
|
||||||
if isinstance(category, str):
|
if isinstance(category, str) and category != "":
|
||||||
return [category]
|
return [category]
|
||||||
else:
|
else:
|
||||||
return []
|
return []
|
||||||
@ -144,7 +142,100 @@ class Cleaner:
|
|||||||
def time(time_entry):
|
def time(time_entry):
|
||||||
if time_entry is None:
|
if time_entry is None:
|
||||||
return None
|
return None
|
||||||
|
elif isinstance(time_entry, timedelta):
|
||||||
|
pretty_print_timedelta(time_entry)
|
||||||
elif isinstance(time_entry, datetime):
|
elif isinstance(time_entry, datetime):
|
||||||
print(time_entry)
|
print(time_entry)
|
||||||
|
elif isinstance(time_entry, str):
|
||||||
|
if re.match("PT.*H.*M", time_entry):
|
||||||
|
time_delta_object = parse_duration(time_entry)
|
||||||
|
return pretty_print_timedelta(time_delta_object)
|
||||||
else:
|
else:
|
||||||
return str(time_entry)
|
return str(time_entry)
|
||||||
|
|
||||||
|
|
||||||
|
# ! TODO: Cleanup Code Below
|
||||||
|
|
||||||
|
|
||||||
|
def parse_duration(iso_duration):
|
||||||
|
"""Parses an ISO 8601 duration string into a datetime.timedelta instance.
|
||||||
|
Args:
|
||||||
|
iso_duration: an ISO 8601 duration string.
|
||||||
|
Returns:
|
||||||
|
a datetime.timedelta instance
|
||||||
|
"""
|
||||||
|
m = re.match(r"^P(?:(\d+)Y)?(?:(\d+)M)?(?:(\d+)D)?T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+(?:.\d+)?)S)?$", iso_duration)
|
||||||
|
if m is None:
|
||||||
|
raise ValueError("invalid ISO 8601 duration string")
|
||||||
|
|
||||||
|
days = 0
|
||||||
|
hours = 0
|
||||||
|
minutes = 0
|
||||||
|
seconds = 0.0
|
||||||
|
|
||||||
|
# Years and months are not being utilized here, as there is not enough
|
||||||
|
# information provided to determine which year and which month.
|
||||||
|
# Python's time_delta class stores durations as days, seconds and
|
||||||
|
# microseconds internally, and therefore we'd have to
|
||||||
|
# convert parsed years and months to specific number of days.
|
||||||
|
|
||||||
|
if m[3]:
|
||||||
|
days = int(m[3])
|
||||||
|
if m[4]:
|
||||||
|
hours = int(m[4])
|
||||||
|
if m[5]:
|
||||||
|
minutes = int(m[5])
|
||||||
|
if m[6]:
|
||||||
|
seconds = float(m[6])
|
||||||
|
|
||||||
|
return timedelta(days=days, hours=hours, minutes=minutes, seconds=seconds)
|
||||||
|
|
||||||
|
|
||||||
|
def pretty_print_timedelta(t, max_components=None, max_decimal_places=2):
|
||||||
|
"""
|
||||||
|
Print a pretty string for a timedelta.
|
||||||
|
For example datetime.timedelta(days=2, seconds=17280) will be printed as '2 days, 4 hours, 48 minutes'. Setting max_components to e.g. 1 will change this to '2.2 days', where the
|
||||||
|
number of decimal points can also be set.
|
||||||
|
"""
|
||||||
|
time_scales = [
|
||||||
|
timedelta(days=365),
|
||||||
|
timedelta(days=1),
|
||||||
|
timedelta(hours=1),
|
||||||
|
timedelta(minutes=1),
|
||||||
|
timedelta(seconds=1),
|
||||||
|
timedelta(microseconds=1000),
|
||||||
|
timedelta(microseconds=1),
|
||||||
|
]
|
||||||
|
time_scale_names_dict = {
|
||||||
|
timedelta(days=365): "year",
|
||||||
|
timedelta(days=1): "day",
|
||||||
|
timedelta(hours=1): "Hour",
|
||||||
|
timedelta(minutes=1): "Minute",
|
||||||
|
timedelta(seconds=1): "Second",
|
||||||
|
timedelta(microseconds=1000): "millisecond",
|
||||||
|
timedelta(microseconds=1): "microsecond",
|
||||||
|
}
|
||||||
|
count = 0
|
||||||
|
txt = ""
|
||||||
|
first = True
|
||||||
|
for scale in time_scales:
|
||||||
|
if t >= scale:
|
||||||
|
count += 1
|
||||||
|
n = t / scale if count == max_components else int(t / scale)
|
||||||
|
t -= n * scale
|
||||||
|
|
||||||
|
n_txt = str(round(n, max_decimal_places))
|
||||||
|
if n_txt[-2:] == ".0":
|
||||||
|
n_txt = n_txt[:-2]
|
||||||
|
txt += "{}{} {}{}".format(
|
||||||
|
"" if first else " ",
|
||||||
|
n_txt,
|
||||||
|
time_scale_names_dict[scale],
|
||||||
|
"s" if n > 1 else "",
|
||||||
|
)
|
||||||
|
if first:
|
||||||
|
first = False
|
||||||
|
|
||||||
|
if len(txt) == 0:
|
||||||
|
txt = "none"
|
||||||
|
return txt
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from mealie.services.scraper.cleaner import Cleaner
|
from mealie.services.scraper.cleaner import Cleaner
|
||||||
@ -89,3 +90,11 @@ def test_html_with_recipe_data():
|
|||||||
assert url_validation_regex.match(recipe_data["image"])
|
assert url_validation_regex.match(recipe_data["image"])
|
||||||
assert len(recipe_data["recipeIngredient"]) == 13
|
assert len(recipe_data["recipeIngredient"]) == 13
|
||||||
assert len(recipe_data["recipeInstructions"]) == 4
|
assert len(recipe_data["recipeInstructions"]) == 4
|
||||||
|
|
||||||
|
|
||||||
|
def test_time_cleaner():
|
||||||
|
|
||||||
|
my_time_delta = "PT2H30M"
|
||||||
|
return_delta = Cleaner.time(my_time_delta)
|
||||||
|
|
||||||
|
assert return_delta == "2 Hours 30 Minutes"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user