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

Hint user to upgrade every 7 days for HINT_ACTIVITY_NAME #1407

Merged
merged 42 commits into from
Dec 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
894eb47
update
Dec 6, 2023
fc0249b
remove package name
Dec 6, 2023
5277897
conflict
Dec 6, 2023
91cf864
flake8
Dec 6, 2023
83b0d61
typo
Dec 6, 2023
be7e56d
update according to comment
Dec 11, 2023
7abdf12
Merge branch 'main' into chenyin/hint_upgrade
Dec 11, 2023
f7f8d11
add constant and update condition
Dec 13, 2023
67dd035
update
Dec 13, 2023
ba4c177
flake8
Dec 13, 2023
69996a1
Merge branch 'main' into chenyin/hint_upgrade
YingChen1996 Dec 13, 2023
a3f084a
update
Dec 13, 2023
6a502a0
update
Dec 13, 2023
740ec8a
test
Dec 13, 2023
0d7e553
update
Dec 14, 2023
851bcf2
typo
Dec 14, 2023
5d8e1c8
flake8
Dec 14, 2023
5444737
test
Dec 14, 2023
2f777f1
update
Dec 14, 2023
04a6a21
ervert
Dec 14, 2023
8fa7c10
change to thread
Dec 14, 2023
f075f73
update test
Dec 14, 2023
e3e4c59
flake8
Dec 14, 2023
27e8ebd
flake8
Dec 14, 2023
dececa9
remvoe LAST_HINT check
Dec 14, 2023
0c2c38e
remvoe LAST_HINT check
Dec 14, 2023
73486e3
test in mac system
Dec 14, 2023
a84f728
revert
Dec 14, 2023
124b5b6
Merge branch 'main' into chenyin/hint_upgrade
YingChen1996 Dec 14, 2023
827cb54
Merge branch 'main' into chenyin/hint_upgrade
Dec 20, 2023
c898b7c
update
Dec 20, 2023
fd4a46c
add timeout
Dec 20, 2023
c8f16a8
change to log
Dec 20, 2023
3a46cf2
change to log
Dec 20, 2023
9bcc3f2
Merge branch 'main' into chenyin/hint_upgrade
Dec 21, 2023
021b6ef
remove lock and no wait pypi
Dec 21, 2023
b8bf5ca
update test
Dec 21, 2023
bad7b08
flake8
Dec 21, 2023
6d2aefb
remove no need log
Dec 21, 2023
97d7a10
flake8
Dec 21, 2023
16906af
Merge branch 'main' into chenyin/hint_upgrade
Dec 21, 2023
c5c1035
Merge branch 'main' into chenyin/hint_upgrade
YingChen1996 Dec 21, 2023
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
9 changes: 9 additions & 0 deletions src/promptflow/promptflow/_constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,12 @@ class AvailableIDE:

USER_AGENT = "USER_AGENT"
PF_USER_AGENT = "PF_USER_AGENT"

CLI_PACKAGE_NAME = 'promptflow'
CURRENT_VERSION = 'current_version'
LATEST_VERSION = 'latest_version'
LAST_HINT_TIME = 'last_hint_time'
LAST_CHECK_TIME = 'last_check_time'
PF_VERSION_CHECK = "pf_version_check.json"
HINT_INTERVAL_DAY = 7
GET_PYPI_INTERVAL_DAY = 7
7 changes: 6 additions & 1 deletion src/promptflow/promptflow/_sdk/_telemetry/activity.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@
import uuid
from contextvars import ContextVar
from datetime import datetime
from concurrent.futures import ThreadPoolExecutor

from promptflow._sdk._telemetry.telemetry import TelemetryMixin
from promptflow._sdk._utils import ClientUserAgentUtil
from promptflow._utils.version_hint_utils import hint_for_update, check_latest_version, HINT_ACTIVITY_NAME


class ActivityType(object):
Expand Down Expand Up @@ -170,8 +172,11 @@ def wrapper(self, *args, **kwargs):
# update activity name according to kwargs.
_activity_name = update_activity_name(activity_name, kwargs=kwargs)
with log_activity(logger, _activity_name, activity_type, custom_dimensions):
if _activity_name in HINT_ACTIVITY_NAME:
hint_for_update()
with ThreadPoolExecutor() as pool:
pool.submit(check_latest_version)
return f(self, *args, **kwargs)

return wrapper

return monitor
87 changes: 87 additions & 0 deletions src/promptflow/promptflow/_utils/version_hint_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# ---------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# ---------------------------------------------------------
import datetime
import json
import logging

from promptflow._constants import (LAST_HINT_TIME, LAST_CHECK_TIME, PF_VERSION_CHECK, CLI_PACKAGE_NAME,
HINT_INTERVAL_DAY, GET_PYPI_INTERVAL_DAY, LATEST_VERSION, CURRENT_VERSION)
from promptflow._sdk._constants import HOME_PROMPT_FLOW_DIR


HINT_ACTIVITY_NAME = ["pf.flows.test", "pf.runs.create_or_update", "pfazure.flows.create_or_update",
"pfazure.runs.create_or_update"]
logger = logging.getLogger(__name__)


def get_cached_versions():
from promptflow._sdk._utils import read_write_by_user
(HOME_PROMPT_FLOW_DIR / PF_VERSION_CHECK).touch(mode=read_write_by_user(), exist_ok=True)
with open(HOME_PROMPT_FLOW_DIR / PF_VERSION_CHECK, "r") as f:
try:
cached_versions = json.load(f)
except json.decoder.JSONDecodeError:
cached_versions = {}
return cached_versions


def dump_cached_versions(cached_versions):
with open(HOME_PROMPT_FLOW_DIR / PF_VERSION_CHECK, "w") as f:
json.dump(cached_versions, f)


def get_latest_version_from_pypi(package_name):
pypi_url = f"https://pypi.org/pypi/{package_name}/json"
try:
import requests
response = requests.get(pypi_url, timeout=3)
if response.status_code == 200:
data = response.json()
latest_version = data["info"]["version"]
return latest_version
else:
return None
except Exception as ex: # pylint: disable=broad-except
logger.debug(f"Failed to get the latest version from '{pypi_url}'. {str(ex)}")
return None


def check_latest_version():
""" Get the latest versions from a cached file"""
cached_versions = get_cached_versions()
last_check_time = datetime.datetime.strptime(cached_versions[LAST_CHECK_TIME], '%Y-%m-%d %H:%M:%S.%f') \
if LAST_CHECK_TIME in cached_versions else None

if last_check_time is None or (datetime.datetime.now() >
last_check_time + datetime.timedelta(days=GET_PYPI_INTERVAL_DAY)):
version = get_latest_version_from_pypi(CLI_PACKAGE_NAME)
if version is not None:
cached_versions[LATEST_VERSION] = version
cached_versions[LAST_CHECK_TIME] = str(datetime.datetime.now())
dump_cached_versions(cached_versions)


def hint_for_update():
"""
Check if there is a new version of prompt flow available every 7 days. IF yes, log debug info to hint
customer to upgrade package.
"""

cached_versions = get_cached_versions()
last_hint_time = datetime.datetime.strptime(
cached_versions[LAST_HINT_TIME],
'%Y-%m-%d %H:%M:%S.%f'
) if LAST_HINT_TIME in cached_versions else None
if last_hint_time is None or (datetime.datetime.now() >
last_hint_time + datetime.timedelta(days=HINT_INTERVAL_DAY)):
from promptflow import __version__ as local_version
cached_versions[CURRENT_VERSION] = local_version
if LATEST_VERSION in cached_versions:
from packaging.version import parse
if parse(cached_versions[CURRENT_VERSION]) < parse(cached_versions[LATEST_VERSION]):
cached_versions[LAST_HINT_TIME] = str(datetime.datetime.now())
message = (f"New prompt flow version available: promptflow-{cached_versions[LATEST_VERSION]}. Running "
f"'pip install --upgrade promptflow' to update.")
logger.debug(message)
dump_cached_versions(cached_versions)
16 changes: 16 additions & 0 deletions src/promptflow/tests/sdk_cli_test/unittests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import threading
from pathlib import Path
from unittest.mock import patch
import datetime

import mock
import pandas as pd
Expand All @@ -22,6 +23,7 @@
_calculate_column_widths,
list_of_dict_to_nested_dict,
)
from promptflow._constants import PF_VERSION_CHECK
from promptflow._sdk._constants import HOME_PROMPT_FLOW_DIR, PROMPT_FLOW_HOME_DIR_ENV_VAR
from promptflow._sdk._errors import GenerateFlowToolsJsonError
from promptflow._sdk._telemetry.logging_handler import get_scrubbed_cloud_role
Expand All @@ -36,6 +38,7 @@
snake_to_camel,
)
from promptflow._utils.load_data import load_data
from promptflow._utils.version_hint_utils import hint_for_update, check_latest_version

TEST_ROOT = Path(__file__).parent.parent.parent
CONNECTION_ROOT = TEST_ROOT / "test_configs/connections"
Expand Down Expand Up @@ -199,6 +202,19 @@ def test_concurrent_execution_of_refresh_connections_dir(self, concurrent_count)
for thread in threads:
thread.join()

@pytest.mark.parametrize("concurrent_count", [1, 2, 4, 8])
def test_concurrent_hint_for_update(self, concurrent_count):
from concurrent.futures import ThreadPoolExecutor
with patch('promptflow._utils.version_hint_utils.datetime') as mock_datetime:
mock_datetime.datetime.now.return_value = datetime.datetime.now()
mock_datetime.datetime.strptime.return_value = datetime.datetime.now() - datetime.timedelta(days=8)
mock_datetime.timedelta.return_value = datetime.timedelta(days=7)
hint_for_update()
with ThreadPoolExecutor(max_workers=concurrent_count) as pool:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does max_workers impact check_latest_version's behavior?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, this is added when hint_for_update and check_latest_version need to be run in one thread pool and I need check the file is well writen in max_workers. Will update this test in later pr.

pool.submit(check_latest_version)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will there be cases check_latest_version not finished when running the following line?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, we won't check since Clement suggests may not break user because of get pypi.

assert Path(HOME_PROMPT_FLOW_DIR / PF_VERSION_CHECK).exists()

@pytest.mark.parametrize(
"data_path",
[
Expand Down