Skip to content

Commit

Permalink
fix issues for v2.7, add pidfile and down/restart command
Browse files Browse the repository at this point in the history
  • Loading branch information
voidZXL committed Dec 10, 2024
1 parent 124a954 commit 476c70f
Show file tree
Hide file tree
Showing 22 changed files with 386 additions and 129 deletions.
14 changes: 7 additions & 7 deletions tests/test_7_ops/test_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ def test_django_operations(self, django_wsgi_process):
assert inst.language == 'python'
# assert inst.backend_version == django.__version__
# maybe the instance is cached in local, we just don't test it for now
assert '2.6.0' <= inst.utilmeta_version <= utilmeta.__version__
assert '2.6.0' <= inst.utilmeta_version

with cli.Client(base_url='http://127.0.0.1:9091') as client:
add = client.get('/api-ninja/add?a=1&b=2')
Expand Down Expand Up @@ -118,7 +118,7 @@ def test_django_asgi_operations(self, django_asgi_process):
assert inst.language == 'python'
# assert inst.backend_version == django.__version__
# maybe the instance is cached in local, we just don't test it for now
assert '2.6.0' <= inst.utilmeta_version <= utilmeta.__version__
assert '2.6.0' <= inst.utilmeta_version

def test_fastapi_operations(self, fastapi_process):
with OperationsClient(base_url='http://127.0.0.1:9092/api/v1/ops', base_headers={
Expand Down Expand Up @@ -146,7 +146,7 @@ def test_fastapi_operations(self, fastapi_process):
assert inst.backend == 'fastapi'
assert inst.language == 'python'
# assert inst.backend_version == fastapi.__version__
assert '2.6.0' <= inst.utilmeta_version <= utilmeta.__version__
assert '2.6.0' <= inst.utilmeta_version

with cli.Client(base_url='http://127.0.0.1:9092') as client:
hello = client.get('/hello')
Expand Down Expand Up @@ -182,7 +182,7 @@ def test_flask_operations(self, flask_process):
assert 'flask' in inst.backend
# apiflask
assert inst.language == 'python'
assert '2.6.0' <= inst.utilmeta_version <= utilmeta.__version__
assert '2.6.0' <= inst.utilmeta_version

with cli.Client(base_url='http://127.0.0.1:9093') as client:
hello = client.get('/hello')
Expand Down Expand Up @@ -216,7 +216,7 @@ def test_sanic_operations(self, sanic_process):
assert inst.backend == 'sanic'
# assert inst.backend_version == sanic.__version__
assert inst.language == 'python'
assert '2.6.0' <= inst.utilmeta_version <= utilmeta.__version__
assert '2.6.0' <= inst.utilmeta_version

with cli.Client(base_url='http://127.0.0.1:9094') as client:
hello = client.get('/sanic')
Expand All @@ -243,7 +243,7 @@ def test_tornado_operations(self, tornado_process):
inst = inst_resp.result[0]
assert inst.backend == 'tornado'
assert inst.language == 'python'
assert '2.6.0' <= inst.utilmeta_version <= utilmeta.__version__
assert '2.6.0' <= inst.utilmeta_version

def test_utilmeta_operations(self, utilmeta_process):
with OperationsClient(base_url='http://127.0.0.1:9090/api/ops') as client:
Expand All @@ -265,4 +265,4 @@ def test_utilmeta_operations(self, utilmeta_process):
inst_resp = client.get_instances()
inst = inst_resp.result[0]
assert inst.language == 'python'
assert '2.6.0' <= inst.utilmeta_version <= utilmeta.__version__
assert '2.6.0' <= inst.utilmeta_version
2 changes: 1 addition & 1 deletion utilmeta/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
__website__ = 'https://utilmeta.com'
__homepage__ = 'https://utilmeta.com/py'
__author__ = 'Xulin Zhou (@voidZXL)'
__version__ = '2.7.0-alpha'
__version__ = '2.7.0'


def version_info() -> str:
Expand Down
10 changes: 5 additions & 5 deletions utilmeta/bin/commands/base.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from typing import Optional

from ..base import BaseCommand, command
from utilmeta import UtilMeta
from utilmeta.utils import search_file, path_join, load_ini, read_from, import_obj
Expand Down Expand Up @@ -50,13 +52,11 @@ def service_ref(self):
return self.service_config.get('service')

@property
def main_file(self):
def main_file(self) -> Optional[str]:
file: str = self.service_config.get('main')
if not file:
return file
if file.endswith('.py'):
return file
return file + '.py'
return None
return os.path.join(self.service.project_dir, file if file.endswith('.py') else f'{file}.py')

@property
def application_ref(self):
Expand Down
50 changes: 46 additions & 4 deletions utilmeta/bin/commands/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,47 @@
import os
from ..constant import RED, META_INI, BLUE
from utilmeta.bin import template as package
from utilmeta.utils import read_from, write_to, import_obj, check_requirement
from utilmeta.utils import read_from, write_to, import_obj, requires
import shutil
import re

TEMP_PATH = package.__path__[0]

DEFAULT_GIT_IGNORE = """
.env
*.env
*.whl
.idea
.vscode
*/.idea
*/.vscode
*/__pycache__
__pycache__/
*.py[cod]
.coverage
.obsidian
*/.coverage
*/.obsidian
*.sqlite3
*.sqlite
*.db
*/restats
/.pytest_cache
.cache
*.pid
*.sock
*.log
*.pem
*.crt
node_modules
npm-debug.log*
yarn-debug.log*
yarn-error.log*
yarn.lock
"""


class SetupCommand(BaseServiceCommand):
SERVER_BACKENDS = 'utilmeta.core.server.backends'
Expand Down Expand Up @@ -92,13 +127,13 @@ def setup(self, name: str = '', *,
import_obj(f'{self.SERVER_BACKENDS}.{self.backend}')
except ModuleNotFoundError:
if self.backend in self.DEFAULT_SUPPORTS:
check_requirement(self.backend, install_when_require=True)
requires(self.backend)
else:
print(f'backend: {repr(self.backend)} not supported or not installed, please enter again')
self.backend = None

if self.backend == 'starlette':
check_requirement('uvicorn', install_when_require=True)
# if self.backend == 'starlette':
# check_requirement('uvicorn', install_when_require=True)

print(f'Enter the production host of your service (default: {self.default_host})')
self.host = input('>>> ') or self.default_host
Expand Down Expand Up @@ -130,6 +165,13 @@ def setup(self, name: str = '', *,
template=template
))

# add gitignore file
write_to(os.path.join(project_path, '.gitignore'), DEFAULT_GIT_IGNORE)
requirements = ['utilmeta']
if self.backend:
requirements.append(self.backend)
write_to(os.path.join(project_path, 'requirements.txt'), '\n'.join(requirements))

print(f'UtilMeta project <{BLUE % self.project_name}> successfully setup at path: {project_path}')

def render(self, content, with_operations: bool = False, template: str = None) -> str:
Expand Down
57 changes: 53 additions & 4 deletions utilmeta/bin/meta.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@
from .commands.setup import SetupCommand
from .commands.base import BaseServiceCommand
from utilmeta import __version__
from utilmeta.utils import run, import_obj
from utilmeta.utils import run, import_obj, kill, Error
from .constant import BLUE, RED, YELLOW
import sys
import psutil


class MetaCommand(BaseServiceCommand):
Expand Down Expand Up @@ -53,6 +54,7 @@ def init(self,
app: str = Arg(alias='--app', default=None),
service: str = Arg(alias='--service', default=None),
main_file: str = Arg(alias='--main', default=None),
pid_file: str = Arg(alias='--pid', default='service.pid'),
):
"""
Initialize utilmeta project with a meta.ini file
Expand All @@ -79,9 +81,11 @@ def init(self,
app_obj = import_obj(app)
if inspect.ismodule(app_obj):
raise ValueError(f'--app should be a python application object, got module: {app_obj}')
print(f'Initializing UtilMeta project with python application: {BLUE % app}')
break
except Exception as e:
err = Error(e)
err.setup()
print(err.message)
print(RED % f'python application reference: {repr(app)} failed to load: {e}')
app = None

Expand All @@ -93,10 +97,14 @@ def init(self,
settings = dict(app=app)
if name:
settings['name'] = name
if service:
settings['service'] = service
if main_file:
settings['main'] = main_file
if service:
settings['service'] = service
if pid_file:
settings['pidfile'] = pid_file

print(f'Initializing UtilMeta project [{BLUE % name}] with python application: {BLUE % app}')

from utilmeta.utils import write_config
write_config({
Expand Down Expand Up @@ -199,6 +207,47 @@ def run(self,

run(cmd)

@command
def down(self):
pid = self.service.pid
if not pid:
if self.service.pid_file:
print(RED % f'meta down: PID not found in pidfile, service may not started yet')
else:
print(RED % f'meta down: requires pidfile set in meta.ini, no pid found')
exit(1)
try:
proc = psutil.Process(pid)
except psutil.NoSuchProcess:
print(f'meta down: service [{self.service.name}](pid={pid}) already stopped')
return
except psutil.Error as e:
print(RED % f'meta down: load process: {pid} failed with error: {e}')
exit(1)
proc.kill()
print(f'meta down: service [{self.service.name}](pid={pid}) stopped')

@command
def restart(self,
connect: bool = Arg('-c', default=False),
log: str = Arg('--log', default='service.log'),
):
pid = self.service.pid
if not pid:
if self.service.pid_file:
return self.run(daemon=True, connect=connect, log=log)
print(RED % f'meta restart: requires pidfile set in meta.ini, no pid found')
exit(1)
try:
proc = psutil.Process(pid)
except psutil.NoSuchProcess:
return self.run(daemon=True, connect=connect, log=log)
except psutil.Error as e:
print(RED % f'meta restart: load process: {pid} failed with error: {e}')
exit(1)
proc.kill()
return self.run(daemon=True, connect=connect, log=log)


def main():
MetaCommand(*sys.argv)()
Expand Down
3 changes: 2 additions & 1 deletion utilmeta/bin/template/full/meta.ini
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
[utilmeta]
main = main
app = main:app
service = config.service:service
service = config.service:service
pidfile = service.pid
5 changes: 3 additions & 2 deletions utilmeta/bin/template/lite/meta.ini
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
[service]
[utilmeta]
main = server
app = server:app
service = server:service
service = server:service
pidfile = service.pid
1 change: 1 addition & 0 deletions utilmeta/conf/preference.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ def __init__(
# ---------
orm_default_save_with_relations: bool = True,
orm_default_query_distinct: Optional[bool] = None,
# orm_schema_integrity_error_cls: Optional[Type[Exception]] = None,
# orm_default_filter_required: Optional[bool] = False,
# orm_default_field_fail_silently: bool = False,
):
Expand Down
23 changes: 16 additions & 7 deletions utilmeta/core/orm/databases/encode.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from .base import BaseDatabaseAdaptor
from typing import Mapping, TYPE_CHECKING
import re
from utilmeta.utils import requires

if TYPE_CHECKING:
from .config import Database
Expand Down Expand Up @@ -252,11 +253,19 @@ def transaction(self, savepoint=None, isolation=None, force_rollback: bool = Fal
return _Transaction(db.connection, force_rollback=force_rollback, isolation=isolation)

def check(self):
if self.config.is_mysql:
requires(
MySQLdb='mysqlclient'
)
elif self.config.is_postgresql:
requires(
psycopg='"psycopg[binary,pool]"',
psycopg2='psycopg2'
)
if self.async_engine:
from utilmeta.utils import check_requirement
check_requirement(self.async_engine, install_when_require=True)
try:
from databases import Database
except (ModuleNotFoundError, ImportError) as e:
raise e.__class__(f'{self.__class__} as database adaptor requires to install databases. '
f'use pip install databases[{self.async_engine}]') from e
requires(self.async_engine)
# try:
# from databases import Database
# except (ModuleNotFoundError, ImportError) as e:
# raise e.__class__(f'{self.__class__} as database adaptor requires to install databases. '
# f'use pip install databases[{self.async_engine}]') from e
2 changes: 1 addition & 1 deletion utilmeta/core/server/backends/django/adaptor.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ def process_request(self, django_request):

def process_response(self, django_response):
request = self.request or _current_request.get(None)
response = self.request or _current_response.get(None)
response = self.response or _current_response.get(None)
_current_request.set(None)
_current_response.set(None)

Expand Down
1 change: 1 addition & 0 deletions utilmeta/core/server/backends/starlette.py
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,7 @@ def f(request: StarletteRequest, _default: bool = False):
req.adaptor.update_context(response=resp)
return self.response_adaptor_cls.reconstruct(resp)
f.__wrapped__ = utilmeta_api_class

if default:
original_default = app.router.default

Expand Down
Loading

0 comments on commit 476c70f

Please sign in to comment.