Make codebase compatible with Python 3.7
This commit is contained in:
parent
2ad06e8b38
commit
f8db199996
11 changed files with 125 additions and 51 deletions
|
|
@ -5,7 +5,7 @@ Provided you already have [Python3](https://www.python.org/downloads/){ target=_
|
||||||
```sh
|
```sh
|
||||||
pip install great-ai
|
pip install great-ai
|
||||||
```
|
```
|
||||||
> Python 3.8 or later is required.
|
> Python 3.7 or later is required.
|
||||||
|
|
||||||
This will work on all major operating systems.
|
This will work on all major operating systems.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,7 @@ Applying AI is becoming increasingly easier but many case studies have shown tha
|
||||||
- [x] Docker support for deployment
|
- [x] Docker support for deployment
|
||||||
- [x] Deployable Jupyter Notebooks
|
- [x] Deployable Jupyter Notebooks
|
||||||
- [x] Dashboard for online monitoring and analysing traces
|
- [x] Dashboard for online monitoring and analysing traces
|
||||||
|
- [x] Active support for Python 3.7, 3.8, 3.9, and 3.10
|
||||||
|
|
||||||
## Roadmap
|
## Roadmap
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,13 +15,13 @@ from typing import (
|
||||||
overload,
|
overload,
|
||||||
)
|
)
|
||||||
|
|
||||||
from async_lru import alru_cache
|
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
from tqdm.cli import tqdm
|
from tqdm.cli import tqdm
|
||||||
from typing_extensions import Literal # <= Python 3.7
|
from typing_extensions import Literal # <= Python 3.7
|
||||||
|
|
||||||
from ..constants import DASHBOARD_PATH
|
from ..constants import DASHBOARD_PATH
|
||||||
from ..context import get_context
|
from ..context import get_context
|
||||||
|
from ..external.async_lru import alru_cache
|
||||||
from ..helper import freeze_arguments, get_function_metadata_store, snake_case_to_text
|
from ..helper import freeze_arguments, get_function_metadata_store, snake_case_to_text
|
||||||
from ..models.use_model import model_versions
|
from ..models.use_model import model_versions
|
||||||
from ..parameters.automatically_decorate_parameters import (
|
from ..parameters.automatically_decorate_parameters import (
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,41 @@ def create_dash_app(function_name: str, version: str, function_docs: str) -> Fla
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
execution_time_histogram_container = html.Div()
|
||||||
|
configuration_container = html.Div(
|
||||||
|
className="configuration-container",
|
||||||
|
)
|
||||||
|
table = get_traces_table()
|
||||||
|
traces_table_container = html.Div(
|
||||||
|
[
|
||||||
|
html.Header(
|
||||||
|
[
|
||||||
|
html.H2("Latest traces"),
|
||||||
|
html.P(
|
||||||
|
"Recent traces and aggregated metrics are presented below. Try filtering the table."
|
||||||
|
),
|
||||||
|
html.A(
|
||||||
|
"Filtering syntax.",
|
||||||
|
href="https://dash.plotly.com/datatable/filtering",
|
||||||
|
target="_blank",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
table,
|
||||||
|
],
|
||||||
|
className="traces-table-container",
|
||||||
|
)
|
||||||
|
parallel_coordinates = dcc.Graph(
|
||||||
|
className="parallel-coordinates", config={"displaylogo": False}
|
||||||
|
)
|
||||||
|
interval = dcc.Interval(
|
||||||
|
interval=2 * 1000, # in milliseconds
|
||||||
|
n_intervals=0,
|
||||||
|
max_intervals=1
|
||||||
|
if get_context().is_production
|
||||||
|
else -1, # will be incremented in production upon each successful request
|
||||||
|
)
|
||||||
|
|
||||||
app.layout = html.Main(
|
app.layout = html.Main(
|
||||||
[
|
[
|
||||||
html.Div(
|
html.Div(
|
||||||
|
|
@ -49,43 +84,15 @@ def create_dash_app(function_name: str, version: str, function_docs: str) -> Fla
|
||||||
function_docs=function_docs,
|
function_docs=function_docs,
|
||||||
accent_color=accent_color,
|
accent_color=accent_color,
|
||||||
),
|
),
|
||||||
execution_time_histogram_container := html.Div(),
|
execution_time_histogram_container,
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
configuration_container := html.Div(
|
configuration_container,
|
||||||
className="configuration-container",
|
traces_table_container,
|
||||||
),
|
parallel_coordinates,
|
||||||
traces_table_container := html.Div(
|
|
||||||
[
|
|
||||||
html.Header(
|
|
||||||
[
|
|
||||||
html.H2("Latest traces"),
|
|
||||||
html.P(
|
|
||||||
"Recent traces and aggregated metrics are presented below. Try filtering the table."
|
|
||||||
),
|
|
||||||
html.A(
|
|
||||||
"Filtering syntax.",
|
|
||||||
href="https://dash.plotly.com/datatable/filtering",
|
|
||||||
target="_blank",
|
|
||||||
),
|
|
||||||
]
|
|
||||||
),
|
|
||||||
table := get_traces_table(),
|
|
||||||
],
|
|
||||||
className="traces-table-container",
|
|
||||||
),
|
|
||||||
parallel_coordinates := dcc.Graph(
|
|
||||||
className="parallel-coordinates", config={"displaylogo": False}
|
|
||||||
),
|
|
||||||
html.Div(className="space-filler"),
|
html.Div(className="space-filler"),
|
||||||
get_footer(),
|
get_footer(),
|
||||||
interval := dcc.Interval(
|
interval,
|
||||||
interval=2 * 1000, # in milliseconds
|
|
||||||
n_intervals=0,
|
|
||||||
max_intervals=1
|
|
||||||
if get_context().is_production
|
|
||||||
else -1, # will be incremented in production upon each successful request
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,3 @@
|
||||||
|
from .cached_property import cached_property
|
||||||
from .human_readable_to_byte import human_readable_to_byte
|
from .human_readable_to_byte import human_readable_to_byte
|
||||||
from .progress_bar import DownloadProgressBar, UploadProgressBar
|
from .progress_bar import DownloadProgressBar, UploadProgressBar
|
||||||
|
|
|
||||||
52
great_ai/large_file/helper/cached_property.py
Normal file
52
great_ai/large_file/helper/cached_property.py
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
from threading import RLock
|
||||||
|
|
||||||
|
_NOT_FOUND = object()
|
||||||
|
|
||||||
|
|
||||||
|
class cached_property:
|
||||||
|
def __init__(self, func): # type: ignore
|
||||||
|
self.func = func
|
||||||
|
self.attrname = None
|
||||||
|
self.__doc__ = func.__doc__
|
||||||
|
self.lock = RLock()
|
||||||
|
|
||||||
|
def __set_name__(self, owner, name): # type: ignore
|
||||||
|
if self.attrname is None:
|
||||||
|
self.attrname = name
|
||||||
|
elif name != self.attrname:
|
||||||
|
raise TypeError(
|
||||||
|
"Cannot assign the same cached_property to two different names "
|
||||||
|
f"({self.attrname!r} and {name!r})."
|
||||||
|
)
|
||||||
|
|
||||||
|
def __get__(self, instance, owner=None): # type: ignore
|
||||||
|
if instance is None:
|
||||||
|
return self
|
||||||
|
if self.attrname is None:
|
||||||
|
raise TypeError(
|
||||||
|
"Cannot use cached_property instance without calling __set_name__ on it."
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
cache = instance.__dict__
|
||||||
|
except AttributeError: # not all objects have __dict__ (e.g. class defines slots)
|
||||||
|
msg = (
|
||||||
|
f"No '__dict__' attribute on {type(instance).__name__!r} "
|
||||||
|
f"instance to cache {self.attrname!r} property."
|
||||||
|
)
|
||||||
|
raise TypeError(msg) from None
|
||||||
|
val = cache.get(self.attrname, _NOT_FOUND)
|
||||||
|
if val is _NOT_FOUND:
|
||||||
|
with self.lock:
|
||||||
|
# check if another thread filled cache while we awaited lock
|
||||||
|
val = cache.get(self.attrname, _NOT_FOUND)
|
||||||
|
if val is _NOT_FOUND:
|
||||||
|
val = self.func(instance)
|
||||||
|
try:
|
||||||
|
cache[self.attrname] = val
|
||||||
|
except TypeError:
|
||||||
|
msg = (
|
||||||
|
f"The '__dict__' attribute on {type(instance).__name__!r} instance "
|
||||||
|
f"does not support item assignment for caching {self.attrname!r} property."
|
||||||
|
)
|
||||||
|
raise TypeError(msg) from None
|
||||||
|
return val
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
|
|
@ -70,7 +71,13 @@ class LargeFileBase(ABC):
|
||||||
|
|
||||||
self._buffering = buffering
|
self._buffering = buffering
|
||||||
self._encoding = encoding
|
self._encoding = encoding
|
||||||
|
|
||||||
|
if errors is not None and sys.version_info[1] < 8:
|
||||||
|
raise RuntimeError(
|
||||||
|
"The `errors` kwarg is only supported in 3.8 <= Python versions."
|
||||||
|
)
|
||||||
self._errors = errors
|
self._errors = errors
|
||||||
|
|
||||||
self._newline = newline
|
self._newline = newline
|
||||||
|
|
||||||
LargeFileBase.cache_path.mkdir(parents=True, exist_ok=True)
|
LargeFileBase.cache_path.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
@ -99,16 +106,20 @@ class LargeFileBase(ABC):
|
||||||
cls.initialized = True
|
cls.initialized = True
|
||||||
|
|
||||||
def __enter__(self) -> IO:
|
def __enter__(self) -> IO:
|
||||||
self._file: IO[Any] = (
|
params = dict(
|
||||||
tempfile.NamedTemporaryFile(
|
|
||||||
mode=self._mode,
|
mode=self._mode,
|
||||||
buffering=self._buffering,
|
buffering=self._buffering,
|
||||||
encoding=self._encoding,
|
encoding=self._encoding,
|
||||||
newline=self._newline,
|
newline=self._newline,
|
||||||
errors=self._errors,
|
|
||||||
delete=False,
|
delete=False,
|
||||||
prefix="large_file-",
|
prefix="large_file-",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if sys.version_info[1] >= 8:
|
||||||
|
params["errors"] = self._errors
|
||||||
|
|
||||||
|
self._file: IO[Any] = (
|
||||||
|
tempfile.NamedTemporaryFile(**params) # type: ignore
|
||||||
if "w" in self._mode
|
if "w" in self._mode
|
||||||
else open(
|
else open(
|
||||||
self.get(),
|
self.get(),
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import re
|
import re
|
||||||
from functools import cached_property
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, List
|
from typing import Any, List
|
||||||
|
|
||||||
|
|
@ -7,7 +6,7 @@ from gridfs import DEFAULT_CHUNK_SIZE, Database, GridFSBucket
|
||||||
from pymongo import MongoClient
|
from pymongo import MongoClient
|
||||||
|
|
||||||
from ...utilities import get_logger
|
from ...utilities import get_logger
|
||||||
from ..helper import DownloadProgressBar, UploadProgressBar
|
from ..helper import DownloadProgressBar, UploadProgressBar, cached_property
|
||||||
from ..models import DataInstance
|
from ..models import DataInstance
|
||||||
from .large_file_base import LargeFileBase
|
from .large_file_base import LargeFileBase
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,10 @@
|
||||||
from functools import cached_property
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, List, Optional
|
from typing import Any, List, Optional
|
||||||
|
|
||||||
import boto3
|
import boto3
|
||||||
|
|
||||||
from ...utilities import get_logger
|
from ...utilities import get_logger
|
||||||
from ..helper import DownloadProgressBar, UploadProgressBar
|
from ..helper import DownloadProgressBar, UploadProgressBar, cached_property
|
||||||
from ..models import DataInstance
|
from ..models import DataInstance
|
||||||
from .large_file_base import LargeFileBase
|
from .large_file_base import LargeFileBase
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,13 +25,13 @@ def test_create_trivial_cases() -> None:
|
||||||
|
|
||||||
def test_create_with_other_decorator() -> None:
|
def test_create_with_other_decorator() -> None:
|
||||||
@GreatAI.create
|
@GreatAI.create
|
||||||
@lru_cache
|
@lru_cache()
|
||||||
def hello_world_1(name: str) -> str:
|
def hello_world_1(name: str) -> str:
|
||||||
return f"Hello {name}!"
|
return f"Hello {name}!"
|
||||||
|
|
||||||
assert hello_world_1("andras").output == "Hello andras!"
|
assert hello_world_1("andras").output == "Hello andras!"
|
||||||
|
|
||||||
@lru_cache
|
@lru_cache()
|
||||||
@GreatAI.create
|
@GreatAI.create
|
||||||
def hello_world_2(name: str) -> str:
|
def hello_world_2(name: str) -> str:
|
||||||
return f"Hello {name}!"
|
return f"Hello {name}!"
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,11 @@ def test_empty() -> None:
|
||||||
|
|
||||||
def test_save() -> None:
|
def test_save() -> None:
|
||||||
path = Path("test.svg")
|
path = Path("test.svg")
|
||||||
path.unlink(missing_ok=True)
|
try:
|
||||||
|
path.unlink()
|
||||||
|
except FileNotFoundError:
|
||||||
|
# missing_ok only exists >= Python 3.8
|
||||||
|
pass
|
||||||
|
|
||||||
evaluate_ranking(
|
evaluate_ranking(
|
||||||
["a", "a", "b", "b", "c", "d", "d", "d"],
|
["a", "a", "b", "b", "c", "d", "d", "d"],
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue