diff --git a/core/morph/api/cloud/client.py b/core/morph/api/cloud/client.py index 86f3c64..8d60a34 100644 --- a/core/morph/api/cloud/client.py +++ b/core/morph/api/cloud/client.py @@ -32,12 +32,24 @@ def __init__(self): from morph.config.project import load_project # avoid circular import - project = load_project(find_project_root_dir()) - if project is None: - raise ValueError("No project found.") - profile = project.profile or "default" + try: + project_root = find_project_root_dir() + except Exception: # noqa + project_root = None - self.project_id = os.environ.get("MORPH_PROJECT_ID", project.project_id or "") + if project_root: + project = load_project(project_root) + else: + project = None + + if project: + profile = project.profile or "default" + else: + profile = "default" + + self.project_id = os.environ.get( + "MORPH_PROJECT_ID", "" if not project else project.project_id or "" + ) self.api_key = os.environ.get("MORPH_API_KEY", "") if not self.api_key: diff --git a/core/morph/api/utils.py b/core/morph/api/utils.py index b229c30..ab040de 100644 --- a/core/morph/api/utils.py +++ b/core/morph/api/utils.py @@ -32,7 +32,8 @@ def convert_file_output( limit = limit if limit is not None else len(df) skip = skip if skip is not None else 0 df = df.iloc[skip : skip + limit] - return {"count": count, "items": df.to_dict(orient="records")} + df = df.replace({float("nan"): None, pd.NaT: None}).to_dict(orient="records") + return {"count": count, "items": df} elif type == "html" or type == "markdown": with open(output_path, "r") as f: return f.read() diff --git a/core/morph/frontend/template/package.json b/core/morph/frontend/template/package.json index b7bc68d..d1f3ef8 100644 --- a/core/morph/frontend/template/package.json +++ b/core/morph/frontend/template/package.json @@ -25,17 +25,15 @@ "@types/react-dom": "^18.3.0", "@typescript-eslint/eslint-plugin": "^7.15.0", "@typescript-eslint/parser": "^7.15.0", - "@morph-data/components": "0.1.2-beta.3", + "@morph-data/components": "0.1.3-beta.5", "@vitejs/plugin-react-swc": "^3.5.0", "class-variance-authority": "^0.7.1", "eslint": "^8.57.0", "eslint-plugin-react-hooks": "^4.6.2", "eslint-plugin-react-refresh": "^0.4.7", "react-error-boundary": "^4.1.2", - "rehype-pretty-code": "^0.14.0", "rehype-slug": "^6.0.0", "remark-gfm": "^4.0.0", - "shiki": "^1.24.1", "typescript": "^5.2.2", "vite": "^5.3.4", "vite-plugin-restart": "^0.4.2" diff --git a/core/morph/frontend/template/src/custom-mdx-components.tsx b/core/morph/frontend/template/src/custom-mdx-components.tsx index 53c8f1f..0992a13 100644 --- a/core/morph/frontend/template/src/custom-mdx-components.tsx +++ b/core/morph/frontend/template/src/custom-mdx-components.tsx @@ -19,6 +19,7 @@ import { SelectItems, DatePicker, DateRangePicker, + Pre, } from "@morph-data/components"; import { MDXComponents } from "mdx/types"; @@ -43,4 +44,5 @@ export const customMDXComponents: MDXComponents = { Chat, DatePicker, DateRangePicker, + pre: Pre, }; diff --git a/core/morph/frontend/template/vite.config.ts b/core/morph/frontend/template/vite.config.ts index 09b9936..52f6140 100644 --- a/core/morph/frontend/template/vite.config.ts +++ b/core/morph/frontend/template/vite.config.ts @@ -4,14 +4,10 @@ import { resolve } from "path"; import ViteRestart from "vite-plugin-restart"; import mdx from "@mdx-js/rollup"; import remarkGfm from "remark-gfm"; -import rehypePrettyCode from "rehype-pretty-code"; import rehypeExtractToc from "@stefanprobst/rehype-extract-toc"; import rehypeExtractTocMdxExport from "@stefanprobst/rehype-extract-toc/mdx"; import rehypeSlug from "rehype-slug"; -/** @type {import('rehype-pretty-code').Options} */ -const prettyCodeOptions = { theme: "github-dark" }; - // https://vitejs.dev/config/ export default defineConfig((env) => ({ plugins: [ @@ -22,7 +18,6 @@ export default defineConfig((env) => ({ remarkPlugins: [remarkGfm], rehypePlugins: [ rehypeSlug, - [rehypePrettyCode, prettyCodeOptions], rehypeExtractToc, rehypeExtractTocMdxExport, ], diff --git a/core/morph/include/starter_template/.mock_user_context.json b/core/morph/include/starter_template/.mock_user_context.json new file mode 100644 index 0000000..3ce55a7 --- /dev/null +++ b/core/morph/include/starter_template/.mock_user_context.json @@ -0,0 +1,8 @@ +{ + "user_id": "cea122ea-b240-49d7-ae7f-8b1e3d40dd8f", + "email": "mock_user@morph-data.io", + "username": "mock_user", + "first_name": "Mock", + "last_name": "User", + "roles": ["Admin"] +} diff --git a/core/morph/task/api.py b/core/morph/task/api.py index 6b6df93..f06d64a 100644 --- a/core/morph/task/api.py +++ b/core/morph/task/api.py @@ -1,4 +1,3 @@ -import configparser import os import signal import socket @@ -12,7 +11,6 @@ from dotenv import dotenv_values, load_dotenv from morph.cli.flags import Flags -from morph.constants import MorphConstant from morph.task.base import BaseTask from morph.task.utils.morph import find_project_root_dir, initialize_frontend_dir from morph.task.utils.timezone import TimezoneManager @@ -38,27 +36,6 @@ def __init__(self, args: Flags): os.environ["MORPH_LOCAL_DEV_MODE"] = "true" - config_path = MorphConstant.MORPH_CRED_PATH - has_config = os.path.exists(config_path) - - if has_config: - # read credentials - config = configparser.ConfigParser() - config.read(config_path) - if not config.sections(): - click.echo( - click.style( - f"Error: No credentials entries found in {config_path}.", - fg="red", - bg="yellow", - ) - ) - sys.exit(1) # 1: General errors - - # set api key - self.api_key: str = config.get("default", "api_key", fallback="") - os.environ["MORPH_API_KEY"] = self.api_key - # load environment variables from .env file self.project_root = find_project_root_dir() dotenv_path = os.path.join(self.project_root, ".env") diff --git a/core/morph/task/new.py b/core/morph/task/new.py index e77af71..6b18366 100644 --- a/core/morph/task/new.py +++ b/core/morph/task/new.py @@ -164,23 +164,27 @@ def run(self): # Prepare the dependency argument if self.is_development: branch = self._get_current_git_branch() or "develop" - dependency = f"git+https://github.com/morph-data/morph.git@{branch}" - else: - dependency = ( - f"morph-data=={morph_data_version}" - if morph_data_version - else "morph-data" + morph_data_dep = ( + 'morph-data = { git = "https://github.com/morph-data/morph.git", rev = "%s" }' + % branch ) + else: + if morph_data_version: + morph_data_dep = f"morph-data = {morph_data_version}" + else: + morph_data_dep = "morph-data" - # Use poetry init with the --dependency option - subprocess.run( - ["poetry", "init", "--no-interaction", "--dependency", dependency], - check=True, + # Generate the pyproject.toml content + pyproject_content = self._generate_pyproject_toml( + project_name=os.path.basename(os.path.normpath(self.project_root)), + morph_data_dependency=morph_data_dep, ) + pyproject_path = Path(self.project_root) / "pyproject.toml" + pyproject_path.write_text(pyproject_content, encoding="utf-8") click.echo( click.style( - f"Added 'morph-data' to pyproject.toml with {dependency}.", + "Added 'morph-data' to pyproject.toml with 'morph-data'.", fg="green", ) ) @@ -301,3 +305,48 @@ def _select_python_version() -> str: ) return selected_version + + @staticmethod + def _get_git_author_info() -> str: + """ + Try to obtain author info from git config user.name and user.email. + Return "Name " if possible, otherwise return empty string. + """ + try: + name = subprocess.check_output( + ["git", "config", "user.name"], text=True + ).strip() + email = subprocess.check_output( + ["git", "config", "user.email"], text=True + ).strip() + if name and email: + return f"{name} <{email}>" + except (subprocess.CalledProcessError, FileNotFoundError): + pass + return "" + + def _generate_pyproject_toml( + self, + project_name: str, + morph_data_dependency: str, + ) -> str: + author = self._get_git_author_info() + authors_line = f'authors = ["{author}"]\n' if author else "" + + return f"""[tool.poetry] +name = "{project_name}" +version = "0.1.0" +description = "" +{authors_line}readme = "README.md" +packages = [ + {{ include = "src" }} +] + +[tool.poetry.dependencies] +python = ">=3.9,<3.13" +{morph_data_dependency} + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" +""" diff --git a/core/morph/task/run.py b/core/morph/task/run.py index 61e76c0..383475d 100644 --- a/core/morph/task/run.py +++ b/core/morph/task/run.py @@ -1,4 +1,3 @@ -import configparser import json import os import sys @@ -14,7 +13,6 @@ from morph.cli.flags import Flags from morph.config.project import MorphProject, load_project -from morph.constants import MorphConstant from morph.task.base import BaseTask from morph.task.utils.logging import get_morph_logger from morph.task.utils.morph import find_project_root_dir @@ -59,27 +57,6 @@ def __init__(self, args: Flags, mode: Optional[Literal["cli", "api"]] = "cli"): self.mode = mode self.api_key = "" - # validate credentials - config_path = MorphConstant.MORPH_CRED_PATH - has_config = os.path.exists(config_path) - - if has_config: - # read credentials - config = configparser.ConfigParser() - config.read(config_path) - if not config.sections(): - click.echo( - click.style( - f"Error: No credentials entries found in {config_path}.", - fg="red", - bg="yellow", - ) - ) - sys.exit(1) # 1: General errors - - self.api_key = config.get("default", "api_key", fallback="") - os.environ["MORPH_API_KEY"] = self.api_key - try: start_dir = filename_or_alias if os.path.isabs(filename_or_alias) else "./" self.project_root = find_project_root_dir(start_dir) diff --git a/pyproject.toml b/pyproject.toml index c194f61..cc269e5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "morph-data" -version = "0.1.2" +version = "0.1.3" description = "Morph is a python-centric full-stack framework for building and deploying data apps." authors = ["Morph "] packages = [