{ "info": { "author": "Roy", "author_email": "wjjroy@outlook.com", "bugtrack_url": null, "classifiers": [ "Development Status :: 4 - Beta", "Intended Audience :: Developers", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Topic :: Software Development :: Build Tools" ], "description": "talos project[^1]\r\n=======================\r\n\r\n[TOC]\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n## \u7279\u6027\r\n\r\nhttps://gitee.com/wu.jianjun/talos/tree/master/release\r\n\r\n\u9879\u76ee\u662f\u4e3b\u8981\u57fa\u4e8efalcon\u548cSQLAlemchy\u5c01\u88c5\uff0c\u63d0\u4f9b\u5e38\u7528\u7684\u9879\u76ee\u5de5\u5177\uff0c\u4fbf\u4e8e\u7528\u6237\u7f16\u5199API\u670d\u52a1\r\n\u9879\u76ee\u63d0\u4f9b\u4e86\u5de5\u5177talos_generator\uff0c\u53ef\u4ee5\u81ea\u52a8\u4e3a\u60a8\u751f\u6210\u57fa\u4e8etalos\u7684api\u5e94\u7528\uff0c\u5e76\u4e14\u76ee\u5f55\u7ed3\u6784\u57fa\u4e8epython\u6807\u51c6\u5305\u7ba1\u7406\r\n\r\n* \u57fa\u4e8efalcon\uff0c\u9ad8\u6548\r\n* \u4f7f\u7528SQLAlchemy\u4f5c\u4e3a\u6570\u636e\u5e93\u540e\u7aef\uff0c\u5feb\u901f\u5207\u6362\u6570\u636e\u5e93\r\n* \u9879\u76ee\u751f\u6210\u5de5\u5177\r\n* \u5feb\u901fRESTfult CRUD API\u5f00\u53d1\r\n* filters\uff0cpagination\uff0corders\u652f\u6301\r\n* validation\u6570\u636e\u6821\u9a8c\r\n* \u5f02\u6b65\u4efb\u52a1\u96c6\u6210[celery]\r\n* \u5b9a\u65f6\u4efb\u52a1\u96c6\u6210[celery]\r\n* \u9891\u7387\u9650\u5236\r\n* \u56fd\u9645\u5316i18n\u652f\u6301\r\n* SMTP\u90ae\u4ef6\u3001AD\u57df\u3001CSV\u5bfc\u51fa\u3001\u7f13\u5b58\u7b49\u5e38\u7528\u6a21\u5757\u96c6\u6210\r\n\r\n\u9996\u5148setup.py install \u5b89\u88c5talos,\u8fd0\u884ctalos\u751f\u6210\u5de5\u5177\u751f\u6210\u9879\u76ee\r\n\r\n\r\n\r\n## \u9879\u76ee\u751f\u6210\r\n\r\n\u5b89\u88c5talos\u540e\uff0c\u4f1a\u751f\u6210talos_generator\u5de5\u5177\uff0c\u6b64\u5de5\u5177\u53ef\u4ee5\u4e3a\u7528\u6237\u5feb\u901f\u751f\u6210\u4e1a\u52a1\u4ee3\u7801\u6846\u67b6\r\n\r\n```bash\r\n> talos_generator\r\n> \u8bf7\u8f93\u5165\u9879\u76ee\u751f\u6210\u76ee\u5f55\uff1a./\r\n> \u8bf7\u8f93\u5165\u9879\u76ee\u540d\u79f0(\u82f1)\uff1acms\r\n> \u8bf7\u8f93\u5165\u751f\u6210\u7c7b\u578b[project,app,\u5176\u4ed6\u5185\u5bb9\u9000\u51fa]\uff1aproject\r\n> \u8bf7\u8f93\u5165\u9879\u76ee\u7248\u672c\uff1a1.2.4\r\n> \u8bf7\u8f93\u5165\u9879\u76ee\u4f5c\u8005\uff1aRoy\r\n> \u8bf7\u8f93\u5165\u9879\u76ee\u4f5c\u8005Email\uff1aroy@test.com\r\n> \u8bf7\u8f93\u5165\u9879\u76ee\u542f\u52a8\u914d\u7f6e\u76ee\u5f55\uff1a./etc #\u6b64\u5904\u586b\u5199\u9ed8\u8ba4\u914d\u7f6e\u8def\u5f84\uff0c\u76f8\u5bf9\u8def\u5f84\u662f\u76f8\u5bf9\u9879\u76ee\u6587\u4ef6\u5939\uff0c\u4e5f\u53ef\u4ee5\u662f\u7edd\u5bf9\u8def\u5f84\r\n> \u8bf7\u8f93\u5165\u9879\u76eeDB\u8fde\u63a5\u4e32\uff1apostgresql+psycopg2://postgres:123456@127.0.0.1/testdb [SQLAlemchy\u7684DB\u8fde\u63a5\u4e32]\r\n### \u521b\u5efa\u9879\u76ee\u76ee\u5f55\uff1a./cms\r\n### \u521b\u5efa\u9879\u76ee\uff1acms(1.2.4)\u901a\u7528\u6587\u4ef6\r\n### \u521b\u5efa\u542f\u52a8\u670d\u52a1\u811a\u672c\r\n### \u521b\u5efa\u542f\u52a8\u914d\u7f6e\uff1a./etc/cms.conf\r\n### \u521b\u5efa\u4e2d\u95f4\u4ef6\u76ee\u5f55\r\n### \u5b8c\u6210\r\n> \u8bf7\u8f93\u5165\u751f\u6210\u7c7b\u578b[project,app,\u5176\u4ed6\u5185\u5bb9\u9000\u51fa]\uff1aapp # \u751f\u6210\u7684APP\u7528\u4e8e\u7f16\u5199\u5b9e\u9645\u4e1a\u52a1\u4ee3\u7801\uff0c\u6216\u624b\u52a8\u7f16\u5199\r\n### \u8bf7\u8f93\u5165app\u540d\u79f0(\u82f1)\uff1auser\r\n### \u521b\u5efaapp\u76ee\u5f55\uff1a./cms/cms/apps\r\n### \u521b\u5efaapp\u811a\u672c\uff1auser\r\n### \u5b8c\u6210\r\n> \u8bf7\u8f93\u5165\u751f\u6210\u7c7b\u578b[project,app,\u5176\u4ed6\u5185\u5bb9\u9000\u51fa]\uff1a\r\n```\r\n\r\n\u9879\u76ee\u751f\u6210\u540e\uff0c\u4fee\u6539\u914d\u7f6e\u6587\u4ef6\uff0c\u6bd4\u5982**./etc/cms.conf\u7684application.names\u914d\u7f6e\uff0c\u5217\u8868\u4e2d\u52a0\u5165\"cms.apps.user\"\u5373\u53ef\u542f\u52a8\u670d\u52a1\u5668\u8fdb\u884c\u8c03\u8bd5**\r\n\r\n\r\n\r\n\r\n## \u5f00\u53d1\u8c03\u8bd5\r\n\u542f\u52a8\u9879\u76ee\u76ee\u5f55\u4e0b\u7684server/simple_server.py\u5373\u53ef\u8fdb\u884c\u8c03\u8bd5\r\n\r\n\r\n\r\n## \u751f\u4ea7\u90e8\u7f72\r\n - \u6e90\u7801\u6253\u5305\r\n\r\n```bash\r\npip install wheel\r\npython setup.py bdist_wheel\r\npip install cms-1.2.4-py2.py3-none-any.whl\r\n```\r\n\r\n - \u542f\u52a8\u670d\u52a1\uff1a\r\n\r\n```bash\r\n# Linux\u90e8\u7f72\u4e00\u822c\u914d\u7f6e\u6587\u4ef6\u90fd\u4f1a\u653e\u5728/etc/cms/\u4e0b\uff0c\u5305\u62eccms.conf\u548cgunicorn.py\u6587\u4ef6\r\n# \u5e76\u786e\u4fdd\u5b89\u88c5gunicorn\r\npip install gunicorn\r\n# \u6b65\u9aa4\u4e00\uff0c\u5bfc\u51fa\u73af\u5883\u53d8\u91cf\uff1a\r\nexport cms_CONF=/etc/cms/cms.conf\r\n# \u6b65\u9aa4\u4e8c\uff0c\r\ngunicorn --pid \"/var/run/cms.pid\" --config \"/etc/cms/gunicorn.py\" \"cms.server.wsgi_server:application\"\r\n```\r\n\r\n\r\n\r\n## API\u5f00\u53d1\u5f15\u5bfc\r\n\r\n### \u57fa\u7840\u5f00\u53d1\u6b65\u9aa4\r\n\r\n#### \u8bbe\u8ba1\u6570\u636e\u5e93\r\n\r\n\u7565\r\n\r\n#### \u5bfc\u51fa\u6570\u636e\u5e93\u6a21\u578b\r\n\r\n\u9879\u76ee\u4e2d\u4f7f\u7528\u7684\u662fSQLAlchemy\uff0c\u4f7f\u7528\u8868\u64cd\u4f5c\u9700\u8981\u5c06\u6570\u636e\u5e93\u5bfc\u51fa\u4e3apython\u5bf9\u8c61\u5b9a\u4e49\uff0c\u8fd9\u6837\u505a\u7684\u597d\u5904\u662f\r\n\r\n1. \u786e\u5b9a\u8868\u7ed3\u6784\uff0c\u5f62\u6210\u5e94\u7528\u4ee3\u7801\u4e0e\u6570\u636e\u5e93\u4e4b\u95f4\u7684\u7248\u672c\u5bf9\u5e94\r\n2. \u4fbf\u4e8e\u7f16\u7a0b\u4e2d\u8868\u8fbe\u6570\u636e\u5e93\u64cd\u4f5c\uff0c\u800c\u5e76\u975e\u4f7f\u7528\u5b57\u7b26\u4e32\r\n\r\n```bash\r\npip install sqlacodegen\r\nsqlacodegen postgresql+psycopg2://postgres:123456@127.0.0.1/testdb --outfile models.py\r\n```\r\n\r\n\u751f\u6210\u7684models.py\u5185\u5bb9\u5927\u81f4\u5982\u4e0b\uff1a\r\n\r\n```python\r\n# coding=utf-8\r\n\r\nfrom __future__ import absolute_import\r\n\r\nfrom sqlalchemy import String\r\nfrom sqlalchemy.ext.declarative import declarative_base\r\n\r\nBase = declarative_base()\r\nmetadata = Base.metadata\r\n\r\nclass User(Base):\r\n __tablename__ = 'user'\r\n\r\n id = Column(String(36), primary_key=True)\r\n name = Column(String(63), nullable=False)\r\n```\r\n\r\n\u5f53\u7136\u8fd9\u4e2a\u5bfc\u51fa\u64cd\u4f5c\u5982\u679c\u5728\u8db3\u591f\u719f\u6089\u7684\u60c5\u51b5\u4e0b\u53ef\u4ee5\u624b\u52a8\u7f16\u5199\uff0c\u4e0d\u9700\u8981\u5bfc\u51fa\u5de5\u5177\r\n\r\n#### \u6570\u636e\u5e93\u6a21\u578b\u7c7b\u7684\u9b54\u6cd5\u7c7b\r\n\r\n\u5c06\u5bfc\u51fa\u7684\u6587\u4ef6\u8868\u5185\u5bb9\u590d\u5236\u5230cms.db.models.py\u4e2d\uff0c\u5e76\u4e3a\u6bcf\u4e2a\u8868\u8bbe\u7f6eDictBase\u57fa\u7c7b\u7ee7\u627f\r\n\r\nmodels.py\u6587\u4ef6\u4e2d\uff0c\u6bcf\u4e2a\u8868\u5bf9\u5e94\u7740\u4e00\u4e2aclass\uff0c\u8fd9\u4f7f\u5f97\u6211\u4eec\u5728\u5f00\u53d1\u4e1a\u52a1\u5904\u7406\u4ee3\u7801\u65f6\u80fd\u660e\u786e\u8868\u5bf9\u5e94\u7684\u5904\u7406\uff0c\u4f46\u5728\u63a5\u53e3\u8fd4\u56de\u4e2d\uff0c\u6211\u4eec\u901a\u5e38\u9700\u8981\u8f6c\u6362\u4e3ajson\uff0c\u56e0\u800c\uff0c\u6211\u4eec\u9700\u8981\u4e3amodels.py\u4e2d\u7684\u6bcf\u4e2a\u8868\u7684\u7c7b\u589e\u52a0\u4e00\u4e2a\u7ee7\u627f\u5173\u7cfb\uff0c\u4ee5\u4fbf\u4e3a\u5b83\u63d0\u4f9b\u8f6c\u6362\u7684\u652f\u6301\r\n\r\n\u5904\u7406\u5b8c\u540e\u7684models.py\u6587\u4ef6\u5982\u4e0b\uff1a\r\n\r\n```python\r\n# coding=utf-8\r\n\r\nfrom __future__ import absolute_import\r\n\r\nfrom sqlalchemy import String\r\nfrom sqlalchemy.ext.declarative import declarative_base\r\nfrom talos.db.dictbase import DictBase\r\n\r\nBase = declarative_base()\r\nmetadata = Base.metadata\r\n\r\nclass User(Base, DictBase):\r\n __tablename__ = 'user'\r\n\r\n id = Column(String(36), primary_key=True)\r\n name = Column(String(63), nullable=False)\r\n\r\nclass UserPhoneNum(Base, DictBase):\r\n __tablename__ = 'user_phone'\r\n\r\n user_id = Column(String(63), nullable=False, primary_key=True)\r\n phone = Column(String(63), nullable=False, primary_key=True)\r\n description = Column(String(255), nullable=True)\r\n```\r\n\r\n\u7ee7\u627f\u4e86\u8fd9\u4e2a\u7c7b\u4e4b\u540e\uff0c\u4e0d\u4ec5\u63d0\u4f9b\u4e86\u8f6c\u6362\u63a5\u53e3json\u7684\u80fd\u529b\uff0c\u8fd8\u63d0\u4f9b\u4e86\u5b57\u6bb5\u63d0\u53d6\u7684\u80fd\u529b\uff0c\u6b64\u9879\u7a0d\u540e\u518d\u8bf4\u660e\uff0c\u6b64\u5904\u4e0d\u5b9a\u4e49\uff0c\u5219\u610f\u5473\u7740\u9ed8\u8ba4\u4f7f\u7528\u8868\u7684\u6240\u6709\u5b57\u6bb5\r\n\r\n#### \u589e\u5220\u6539\u67e5\u7684\u8d44\u6e90\u7c7b\r\n\r\n\u5728cms.db\u4e2d\u65b0\u589eresource.py\u6587\u4ef6\uff0c\u5185\u5bb9\u5982\u4e0b\uff1a\r\n\r\n```python\r\n# coding=utf-8\r\n\r\nfrom __future__ import absolute_import\r\n\r\nfrom talos.db.crud import ResourceBase\r\n\r\nfrom cms.db import models\r\n\r\n\r\nclass User(ResourceBase):\r\n orm_meta = models.User\r\n _primary_keys = 'id'\r\n\r\n\r\nclass UserPhoneNum(ResourceBase):\r\n orm_meta = models.UserPhoneNum\r\n _primary_keys = ('user_id', 'phone')\r\n```\r\n\r\n\u5b8c\u6210\u6b64\u9879\u5b9a\u4e49\u540e\uff0c\u6211\u4eec\u53ef\u4ee5\u4f7f\u7528resource.User\u6765\u8fdb\u884c\u7528\u6237\u8868\u7684\u589e\u5220\u6539\u67e5\uff0c\u800c\u8fd9\u4e9b\u529f\u80fd\u90fd\u662fResourceBase\u9ed8\u8ba4\u63d0\u4f9b\u7684\u80fd\u529b\r\n\r\n\u53ef\u4ee5\u770b\u5230\u6211\u4eec\u6b64\u5904\u5b9a\u4e49\u4e86orm_meta\u548c_primary_keys\u4e24\u4e2a\u7c7b\u5c5e\u6027\uff0c\u9664\u6b64\u4ee5\u5916\u8fd8\u6709\u66f4\u591a\u7c7b\u5c5e\u6027\u53ef\u4ee5\u5e2e\u52a9\u6211\u4eec\u5feb\u901f\u914d\u7f6e\u5e94\u7528\u903b\u8f91\r\n\r\n| \u7c7b\u5c5e\u6027 | \u9ed8\u8ba4\u503c | \u63cf\u8ff0 |\r\n| --------------- | ------ | ------------------------------------------------------------ |\r\n| orm_meta | None | \u8d44\u6e90\u64cd\u4f5c\u7684SQLAlchmey Model\u7c7b[\u8868] |\r\n| _primary_keys | 'id' | \u8868\u5bf9\u5e94\u7684\u4e3b\u952e\u5217\uff0c\u5355\u4e2a\u4e3b\u952e\u65f6\uff0c\u4f7f\u7528\u5b57\u7b26\u4e32\uff0c\u591a\u4e2a\u8054\u5408\u4e3b\u952e\u65f6\u4e3a\u5b57\u7b26\u4e32\u5217\u8868\uff0c\u8fd9\u4e2a\u662f\u4e1a\u52a1\u4e3b\u952e\uff0c\u610f\u5473\u7740\u4f60\u53ef\u4ee5\u5b9a\u4e49\u548c\u6570\u636e\u5e93\u4e3b\u952e\u4e0d\u4e00\u6837\u7684\u5b57\u6bb5\uff08\u524d\u63d0\u662f\u4f60\u8981\u786e\u5b9a\u8fd9\u4e9b\u5b57\u6bb5\u662f\u6709\u552f\u4e00\u6027\u7684\uff09 |\r\n| _default_filter | {} | \u9ed8\u8ba4\u8fc7\u6ee4\u67e5\u8be2\uff0c\u5e38\u7528\u4e8e\u8f6f\u5220\u9664\uff0c\u6bd4\u5982\u6570\u636e\u5220\u9664\u6211\u4eec\u5728\u6570\u636e\u5e93\u5b57\u6bb5\u4e2d\u6807\u8bb0\u4e3ais_deleted=True\uff0c\u90a3\u4e48\u6211\u4eec\u518d\u6b21list\uff0cget\uff0cupdate\uff0cdelete\u7684\u65f6\u5019\u9700\u8981\u9ed8\u8ba4\u8fc7\u6ee4\u8fd9\u4e9b\u6570\u636e\u7684\uff0c\u7b49\u4ef7\u4e8e\u9ed8\u8ba4\u5e26\u6709where is_delete = True |\r\n| _default_order | [] | \u9ed8\u8ba4\u6392\u5e8f\uff0c\u67e5\u8be2\u8d44\u6e90\u65f6\u88ab\u5e94\u7528\uff0c('name', '+id', '-status'), +\u8868\u793a\u9012\u589e\uff0c-\u8868\u793a\u9012\u51cf\uff0c\u9ed8\u8ba4\u9012\u589e |\r\n| _validate | [] | \u6570\u636e\u8f93\u5165\u6821\u9a8c\u89c4\u5219\uff0c\u4e3atalos.db.crud.ColumnValidator\u5bf9\u8c61\u5217\u8868 |\r\n\u4e00\u4e2a_validate\u793a\u4f8b\u5982\u4e0b\uff1a\r\n\r\n```python\r\n ColumnValidator(field='id',\r\n validate_on=['create:M']),\r\n ColumnValidator(field='name',\r\n rule='1, 63',\r\n rule_type='length',\r\n validate_on=['create:M', 'update:O']),\r\n ColumnValidator(field='enabled',\r\n rule=validator.InValidator(['true', 'false', 'True', 'False'])\r\n converter=converter.BooleanConverter(),\r\n validate_on=['create:M', 'update:O']),\r\n```\r\n\r\nColumnValidator\u53ef\u4ee5\u5b9a\u4e49\u7684\u5c5e\u6027\u5982\u4e0b\uff1a\r\n\r\n| \u5c5e\u6027 | \u7c7b\u578b | \u63cf\u8ff0 |\r\n| ------------ | ---------------------------------------------- | ------------------------------------------------------------ |\r\n| field | \u5b57\u7b26\u4e32 | \u5b57\u6bb5\u540d\u79f0 |\r\n| rule | validator\u5bf9\u8c61 \u6216 \u6821\u9a8c\u7c7b\u578brule_type\u6240\u9700\u8981\u7684\u53c2\u6570 | \u5f53rule\u662fvalidator\u7c7b\u578b\u5bf9\u8c61\u65f6\uff0c\u5ffd\u7565 rule_type\u53c2\u6570 |\r\n| rule_type | \u5b57\u7b26\u4e32 | \u7528\u4e8e\u5feb\u901f\u5b9a\u4e49\u6821\u9a8c\u89c4\u5219\uff0c\u9ed8\u8ba4regex\uff0c\u53ef\u9009\u7c7b\u578b\u6709callback\uff0cregex\uff0cemail\uff0cphone\uff0curl\uff0clength\uff0cin\uff0cnotin\uff0cinteger\uff0cfloat\uff0ctype |\r\n| validate_on | \u6570\u7ec4 | \u6821\u9a8c\u573a\u666f\u548c\u5fc5\u8981\u6027, eg. ['create: M', 'update:O']\uff0c\u8868\u793a\u6b64\u5b57\u6bb5\u5728create\u51fd\u6570\u4e2d\u4e3a\u5fc5\u8981\u5b57\u6bb5\uff0cupdate\u51fd\u6570\u4e2d\u4e3a\u53ef\u9009\u5b57\u6bb5 |\r\n| error_msg | \u5b57\u7b26\u4e32 | \u9519\u8bef\u63d0\u793a\u4fe1\u606f\uff0c\u9ed8\u8ba4\u4e3a'%(result)s'\uff0c\u5373validator\u8fd4\u56de\u7684\u62a5\u9519\u4fe1\u606f\uff0c\u7528\u6237\u53ef\u4ee5\u56fa\u5b9a\u5b57\u7b26\u4e32\u6216\u4f7f\u7528\u5e26\u6709%(result)s\u7684\u6a21\u677f\u5b57\u7b26\u4e32 |\r\n| converter | converter\u5bf9\u8c61 | \u6570\u636e\u8f6c\u6362\u5668\uff0c\u5f53\u6570\u636e\u88ab\u6821\u9a8c\u540e\uff0c\u53ef\u80fd\u9700\u8981\u8f6c\u6362\u4e3a\u56fa\u5b9a\u7c7b\u578b\u540e\u624d\u80fd\u8fdb\u884c\u7f16\u7a0b\u5904\u7406\uff0c\u8f6c\u6362\u5668\u53ef\u4ee5\u4e3a\u6b64\u63d0\u4f9b\u81ea\u52a8\u8f6c\u6362\uff0c\u6bd4\u5982\u7528\u6237\u8f93\u5165\u4e3a'2018-01-01 01:01:01'\u5b57\u7b26\u4e32\u65f6\u95f4\uff0c\u7a0b\u5e8f\u9700\u8981\u4e3aDatetime\u7c7b\u578b\uff0c\u5219\u53ef\u4ee5\u4f7f\u7528DateTimeConverter\u8fdb\u884c\u8f6c\u6362 |\r\n| orm_required | \u5e03\u5c14\u503c | \u63a7\u5236\u6b64\u5b57\u6bb5\u662f\u5426\u4f1a\u88ab\u4f20\u9012\u5230\u6570\u636e\u5e93SQL\u4e2d\u53bb |\r\n| aliases | \u6570\u7ec4 | \u5b57\u6bb5\u7684\u522b\u540d |\r\n| nullable | \u5e03\u5c14\u503c | \u63a7\u5236\u6b64\u5b57\u6bb5\u662f\u5426\u53ef\u4ee5\u4e3aNone |\r\n\r\nCRUD\u4f7f\u7528\u65b9\u5f0f:\r\n\r\n```python\r\nresource.User().create({'id': '1', 'name': 'test'})\r\nresource.User().list()\r\nresource.User().list({'name': 'test'})\r\nresource.User().list({'name': {'ilike': 'na'}}, offset=0, limit=5)\r\nresource.User().count()\r\nresource.User().count({'name': 'test'})\r\nresource.User().count({'name': {'ilike': 'na'}})\r\nresource.User().get('1')\r\nresource.User().update('1', {'name': 'test1'})\r\nresource.User().delete('1')\r\nresource.UserPhoneNum().get(('1', '10086'))\r\nresource.UserPhoneNum().delete(('1', '10086'))\r\n```\r\n\r\n\u5185\u90e8\u67e5\u8be2\u901a\u8fc7\u7ec4\u88c5dict\u6765\u5b9e\u73b0\u8fc7\u6ee4\u6761\u4ef6\uff0cfilter\u5728\u8868\u8fbe == \u6216\u8fd9 in \u5217\u8868\u65f6\uff0c\u53ef\u4ee5\u76f4\u63a5\u4f7f\u7528\u4e00\u7ea7\u5b57\u6bb5\u5373\u53ef\uff0c\u5982name\u7b49\u4e8etest\uff1a{'name': 'test'}\r\n\r\nid\u57281,2,3,4\u5185\uff1a{'id': ['1', '2', '3', '4']}\r\n\r\n\u66f4\u590d\u6742\u7684\u67e5\u8be2\u9700\u8981\u901a\u8fc7\u5d4c\u5957dict\u6765\u5b9e\u73b0[^ 5]\uff1a\r\n\r\n- \u7b80\u5355\u7ec4\u5408\uff1a{'\u5b57\u6bb5\u540d\u79f0': {'\u8fc7\u6ee4\u6761\u4ef61': '\u503c', '\u8fc7\u6ee4\u6761\u4ef62': '\u503c'}}\r\n\r\n- \u7b80\u5355$or+\u7ec4\u5408\u67e5\u8be2\uff1a{'$or': [{'\u5b57\u6bb5\u540d\u79f0': {'\u8fc7\u6ee4\u6761\u4ef6': '\u503c'}}, {'\u5b57\u6bb5\u540d\u79f0': {'\u8fc7\u6ee4\u6761\u4ef61': '\u503c', '\u8fc7\u6ee4\u6761\u4ef62': '\u503c'}}]}\r\n\r\n- \u7b80\u5355$and+\u7ec4\u5408\u67e5\u8be2\uff1a{'$and': [{'\u5b57\u6bb5\u540d\u79f0': {'\u8fc7\u6ee4\u6761\u4ef6': '\u503c'}}, {'\u5b57\u6bb5\u540d\u79f0': {'\u8fc7\u6ee4\u6761\u4ef61': '\u503c', '\u8fc7\u6ee4\u6761\u4ef62': '\u503c'}}]}\r\n\r\n- \u590d\u6742$and+$or+\u7ec4\u5408\u67e5\u8be2\uff1a\r\n\r\n {'$and': [\r\n\r\n \u200b {'$or': [{'\u5b57\u6bb5\u540d\u79f0': '\u503c'}, {'\u5b57\u6bb5\u540d\u79f0': {'\u8fc7\u6ee4\u6761\u4ef61': '\u503c', '\u8fc7\u6ee4\u6761\u4ef62': '\u503c'}}]}, \r\n\r\n \u200b {'\u5b57\u6bb5\u540d\u79f0': {'\u8fc7\u6ee4\u6761\u4ef61': '\u503c', '\u8fc7\u6ee4\u6761\u4ef62': '\u503c'}}\r\n\r\n ]}\r\n\r\n| \u8fc7\u6ee4\u6761\u4ef6 | \u503c\u7c7b\u578b | \u542b\u4e49 |\r\n| -------- | --------------- | -------------------------------------------------------------- |\r\n| like | string | \u6a21\u7cca\u67e5\u8be2\uff0c\u5305\u542b\u6761\u4ef6 |\r\n| ilike | string | \u540c\u4e0a\uff0c\u4e0d\u533a\u5206\u5927\u5c0f\u5199 |\r\n| starts | string | \u6a21\u7cca\u67e5\u8be2\uff0c\u4ee5xxxx\u5f00\u5934 |\r\n| istarts | string | \u540c\u4e0a\uff0c\u4e0d\u533a\u5206\u5927\u5c0f\u5199 |\r\n| ends | string | \u6a21\u7cca\u67e5\u8be2\uff0c\u4ee5xxxx\u7ed3\u5c3e |\r\n| iends | string | \u540c\u4e0a\uff0c\u4e0d\u533a\u5206\u5927\u5c0f\u5199 |\r\n| in | list | \u7cbe\u786e\u67e5\u8be2\uff0c\u6761\u4ef6\u5728\u5217\u8868\u4e2d |\r\n| nin | list | \u7cbe\u786e\u67e5\u8be2\uff0c\u6761\u4ef6\u4e0d\u5728\u5217\u8868\u4e2d |\r\n| eq | \u6839\u636e\u5b57\u6bb5\u7c7b\u578b | \u7b49\u4e8e |\r\n| ne | \u6839\u636e\u5b57\u6bb5\u7c7b\u578b | \u4e0d\u7b49\u4e8e |\r\n| lt | \u6839\u636e\u5b57\u6bb5\u7c7b\u578b | \u5c0f\u4e8e |\r\n| lte | \u6839\u636e\u5b57\u6bb5\u7c7b\u578b | \u5c0f\u4e8e\u7b49\u4e8e |\r\n| gt | \u6839\u636e\u5b57\u6bb5\u7c7b\u578b | \u5927\u4e8e |\r\n| gte | \u6839\u636e\u5b57\u6bb5\u7c7b\u578b | \u5927\u4e8e\u7b49\u4e8e |\r\n| nlike | string | \u6a21\u7cca\u67e5\u8be2\uff0c\u4e0d\u5305\u542b |\r\n| nilike | string | \u540c\u4e0a\uff0c\u4e0d\u533a\u5206\u5927\u5c0f\u5199 |\r\n| null | \u4efb\u610f | \u662fNULL\uff0c\u7b49\u540c\u4e8e{'eq': None}\uff0cnull\u4e3b\u8981\u63d0\u4f9bHTTP API\u4e2d\u4f7f\u7528 |\r\n| nnull | \u4efb\u610f | \u4e0d\u662fNULL\uff0c\u7b49\u540c\u4e8e{'ne': None}\uff0cnnull\u4e3b\u8981\u63d0\u4f9bHTTP API\u4e2d\u4f7f\u7528 |\r\n\r\n\u8fc7\u6ee4\u6761\u4ef6\u53ef\u4ee5\u6839\u636e\u4e0d\u540c\u7684\u6570\u636e\u7c7b\u578b\u751f\u6210\u4e0d\u540c\u7684\u67e5\u8be2\u8bed\u53e5\uff0cvarchar\u7c7b\u578b\u7684in\u662f IN ('1', '2') , inet\u7c7b\u578b\u7684in\u662f<<=cidr\r\n\r\n\u4e00\u822c\u7c7b\u578b\u7684eq\u662fcol='value'\uff0cbool\u7c7b\u578b\u7684eq\u662fcol is TRUE\uff0c\u8be6\u89c1talos.db.filter_wrapper\r\n\r\n#### \u4e1a\u52a1api\u63a7\u5236\u7c7b\r\n\r\napi\u7684\u6a21\u5757\u4e3a\uff1acms.apps.user.api\r\n\r\nresource\u5904\u7406\u7684\u662fDB\u7684CRUD\u64cd\u4f5c\uff0c\u4f46\u5f80\u5f80\u4e1a\u52a1\u7c7b\u4ee3\u7801\u9700\u8981\u6709\u590d\u6742\u7684\u5904\u7406\u903b\u8f91\uff0c\u5e76\u4e14\u6d89\u53ca\u591a\u4e2aresource\u7c7b\u7684\u76f8\u4e92\u64cd\u4f5c\uff0c\u56e0\u6b64\u9700\u8981\u5c01\u88c5api\u5c42\u6765\u5904\u7406\u6b64\u7c7b\u903b\u8f91\uff0c\u6b64\u5904\u6211\u4eec\u793a\u4f8b\u6ca1\u6709\u590d\u6742\u903b\u8f91\uff0c\u76f4\u63a5\u6cbf\u7528\u5b9a\u4e49\u5373\u53ef\r\n\r\n```python\r\nUser = resource.User\r\nUserPhoneNum = resource.UserPhoneNum\r\n```\r\n\r\n#### Collection\u548cItem Controller\r\n\r\nController\u6a21\u5757\u4e3a\uff1acms.apps.user.controller\r\n\r\nController\u88ab\u8bbe\u8ba1\u5206\u7c7b\u4e3aCollection\u548cItem 2\u79cd\uff0c\u5206\u522b\u5bf9\u5e94RESTFul\u7684URL\u64cd\u4f5c\uff0c\u6211\u4eec\u5148\u770b\u4e00\u4e2a\u5e38\u89c1\u7684URL\u8bbe\u8ba1\u548c\u64cd\u4f5c\r\n\r\n```bash\r\nPOST /v1/users \u521b\u5efa\u7528\u6237\r\nGET /v1/users \u67e5\u8be2\u7528\u6237\u5217\u8868\r\nPATCH /v1/users/1 \u66f4\u65b0\u7528\u62371\u7684\u4fe1\u606f\r\nDELETE /v1/users/1 \u5220\u9664\u7528\u62371\u7684\u4fe1\u606f\r\nGET /v1/users/1 \u83b7\u53d6\u7528\u62371\u7684\u8be6\u60c5\r\n```\r\n\r\n\u6839\u636e\u5f53\u524d\u7684URL\u89c4\u5f8b\u6211\u4eec\u53ef\u4ee5\u5427\u521b\u5efa\u548c\u67e5\u8be2\u5217\u8868\u4f5c\u4e3a\u4e00\u4e2a\u5c01\u88c5\uff08CollectionController\uff09\uff0c\u800c\u66f4\u65b0\uff0c\u5220\u9664\uff0c\u83b7\u53d6\u8be6\u60c5\u4f5c\u4e3a\u4e00\u4e2a\u5c01\u88c5\uff08ItemController\uff09\uff0c\u800c\u540c\u6837\u7684\uff0c\u5bf9\u4e8e\u8fd9\u6837\u7684\u6807\u51c6\u64cd\u4f5c\uff0ctalos\u540c\u6837\u63d0\u4f9b\u4e86\u9b54\u6cd5\u822c\u7684\u5b9a\u4e49\r\n\r\n```python\r\nclass CollectionUser(CollectionController):\r\n name = 'cms.users'\r\n resource = api.User\r\n\r\n\r\nclass ItemUser(ItemController):\r\n name = 'cms.user'\r\n resource = api.User\r\n```\r\n\r\n#### route\u8def\u7531\u6620\u5c04\r\n\r\nroute\u6a21\u5757\u4e3a\uff1acms.apps.user.route\r\n\r\n\u63d0\u4f9b\u4e86Controller\u540e\uff0c\u6211\u4eec\u8fd8\u9700\u8981\u5c06\u5176\u4e0eURL\u8def\u7531\u8fdb\u884c\u6620\u5c04\u624d\u80fd\u8c03\u7528\uff0croute\u6a21\u5757\u4e2d\uff0c\u5fc5\u987b\u6709add_routes\u51fd\u6570\uff0c\u6ce8\u518capp\u7684\u65f6\u5019\u4f1a\u9ed8\u8ba4\u5bfb\u627e\u8fd9\u4e2a\u51fd\u6570\u6765\u6ce8\u518c\u8def\u7531\r\n\r\n```python\r\ndef add_routes(api):\r\n api.add_route('/v1/users', controller.CollectionUser())\r\n api.add_route('/v1/users/{rid}', controller.ItemUser())\r\n```\r\n\r\n#### \u914d\u7f6e\u542f\u52a8\u52a0\u8f7dapp\r\n\r\n\u6211\u4eec\u5728\u5f15\u5bfc\u5f00\u59cb\u65f6\u521b\u5efa\u7684\u9879\u76ee\u914d\u7f6e\u6587\u4ef6\u5b58\u653e\u5728./etc\u4e2d\uff0c\u6240\u4ee5\u6211\u4eec\u7684\u914d\u7f6e\u6587\u4ef6\u5728./etc/cms.conf\uff0c\u4fee\u6539\r\n\r\n```javascript\r\n...\r\n\"application\": {\r\n \"names\": [\r\n \"cms.apps.user\"]\r\n},\r\n...\r\n```\r\n\r\n#### \u542f\u52a8\u8c03\u8bd5\u6216\u90e8\u7f72\r\n\r\n\u5728\u6e90\u7801\u76ee\u5f55\u4e2d\u6709server\u5305\uff0c\u5176\u4e2dsimple_server\u662f\u7528\u4e8e\u5f00\u53d1\u8c03\u8bd5\u7528\uff0c\u4e0d\u5efa\u8bae\u5728\u751f\u4ea7\u4e2d\u4f7f\u7528\r\n\r\npython simple_server.py\r\n\r\n#### \u6d4b\u8bd5API\r\n\r\n\u542f\u52a8\u540e\u6211\u4eec\u7684\u670d\u52a1\u5df2\u7ecf\u53ef\u4ee5\u5bf9\u5916\u8f93\u51fa\u5566\uff01\r\n\r\n\u90a3\u4e48\u6211\u4eec\u7684API\u5230\u5e95\u63d0\u4f9b\u4e86\u4ec0\u4e48\u6837\u7684\u80fd\u529b\u5462\uff1f\u6211\u4eec\u4ee5user\u4f5c\u4e3a\u793a\u4f8b\u5c55\u793a\r\n\r\n\u521b\u5efa\r\n\r\n```\r\nPOST /v1/users\r\nContent-Type: application/json;charset=UTF-8\r\nHost: 127.0.0.1:9002\r\n\r\n{\r\n \"id\": \"1\",\r\n \"name\": \"test\"\r\n}\r\n```\r\n\r\n\u67e5\u8be2\u5217\u8868\r\n\r\n```\r\nGET /v1/users\r\nHost: 127.0.0.1:9002\r\n\r\n{\r\n \"count\": 1,\r\n \"data\": [\r\n {\r\n \"id\": \"1\",\r\n \"name\": \"test\"\r\n }\r\n ]\r\n}\r\n```\r\n\r\n\u5173\u4e8e\u67e5\u8be2\u5217\u8868\uff0c\u6211\u4eec\u63d0\u4f9b\u4e86\u5f3a\u5927\u7684\u67e5\u8be2\u80fd\u529b\uff0c\u53ef\u4ee5\u6ee1\u8db3\u5927\u90e8\u5206\u7684\u67e5\u8be2\u573a\u666f\r\n\r\n\u83b7\u53d6**\u5217\u8868(\u67e5\u8be2)\u7684\u63a5\u53e3**\u53ef\u4ee5\u4f7f\u7528Query\u53c2\u6570\u8fc7\u6ee4\uff0c\u4f7f\u7528\u8fc7\u6ee4\u5b57\u6bb5=xxx \u6216 \u5b57\u6bb5__\u67e5\u8be2\u6761\u4ef6=xxx\u65b9\u5f0f\u4f20\u9012\r\n\r\n- **\u8fc7\u6ee4\u6761\u4ef6**\r\n\r\n eg.\r\n\r\n ```bash\r\n # \u67e5\u8be2name\u5b57\u6bb5\u7b49\u4e8eabc\r\n name=abc\r\n # \u67e5\u8be2name\u5b57\u6bb5\u5305\u542babc\r\n name__icontains=abc\r\n # \u67e5\u8be2name\u5b57\u6bb5\u5728\u5217\u8868[a, b, c]\u503c\u5185\r\n name=a&name=b&name=c \r\n # \u6216 \r\n name__in=a&name__in=b&name__in=c\r\n # \u67e5\u8be2name\u5b57\u6bb5\u5728\u5217\u8868\u503c\u5185\r\n name[0]=a&name[1]=b&name[2]=c \r\n # \u6216 \r\n name__in[0]=a&name__in[1]=b&name__in[2]=c\r\n ```\r\n\r\n \u540c\u65f6\u652f\u6301\u5168\u62fc\u6761\u4ef6\u548c\u7f29\u5199\u6761\u4ef6\u67e5\u8be2\uff1a\r\n\r\n\r\n| \u5168\u62fc\u6761\u4ef6 | \u7f29\u5199\u6761\u4ef6 | \u542b\u4e49 |\r\n| ------------ | -------- | ------------------------------------------------------------ |\r\n| N/A | | \u7cbe\u786e\u67e5\u8be2\uff0c\u5b8c\u5168\u7b49\u4e8e\u6761\u4ef6\uff0c\u5982\u679c\u591a\u4e2a\u6b64\u6761\u4ef6\u51fa\u73b0\uff0c\u5219\u8ba4\u4e3a\u6761\u4ef6\u5728\u5217\u8868\u4e2d |\r\n| contains | like | \u6a21\u7cca\u67e5\u8be2\uff0c\u5305\u542b\u6761\u4ef6 |\r\n| icontains | ilike | \u540c\u4e0a\uff0c\u4e0d\u533a\u5206\u5927\u5c0f\u5199 |\r\n| startswith | starts | \u6a21\u7cca\u67e5\u8be2\uff0c\u4ee5xxxx\u5f00\u5934 |\r\n| istartswith | istarts | \u540c\u4e0a\uff0c\u4e0d\u533a\u5206\u5927\u5c0f\u5199 |\r\n| endswith | ends | \u6a21\u7cca\u67e5\u8be2\uff0c\u4ee5xxxx\u7ed3\u5c3e |\r\n| iendswith | iends | \u540c\u4e0a\uff0c\u4e0d\u533a\u5206\u5927\u5c0f\u5199 |\r\n| in | in | \u7cbe\u786e\u67e5\u8be2\uff0c\u6761\u4ef6\u5728\u5217\u8868\u4e2d |\r\n| notin | nin | \u7cbe\u786e\u67e5\u8be2\uff0c\u6761\u4ef6\u4e0d\u5728\u5217\u8868\u4e2d |\r\n| equal | eq | \u7b49\u4e8e |\r\n| notequal | ne | \u4e0d\u7b49\u4e8e |\r\n| less | lt | \u5c0f\u4e8e |\r\n| lessequal | lte | \u5c0f\u4e8e\u7b49\u4e8e |\r\n| greater | gt | \u5927\u4e8e |\r\n| greaterequal | gte | \u5927\u4e8e\u7b49\u4e8e |\r\n| excludes | nlike | \u6a21\u7cca\u67e5\u8be2\uff0c\u4e0d\u5305\u542b |\r\n| iexcludes | nilike | \u540c\u4e0a\uff0c\u4e0d\u533a\u5206\u5927\u5c0f\u5199 |\r\n| null | null | \u662fNULL |\r\n| notnull | nnull | \u4e0d\u662fNULL |\r\n| hasany | hasany | *JSONB\u4e13\u7528* \u5305\u542b\u4efb\u610fkey\uff0c\u5982['a','b', 'c'] hasany ['a','d'] |\r\n| hasall | hasall | *JSONB\u4e13\u7528* \u5305\u542b\u6240\u6709key\uff0c\u5982['a','b', 'c'] hasall ['a','c'] |\r\n| within | within | *JSONB\u4e13\u7528* \u88ab\u6307\u5b9ajson\u5305\u542b\u5728\u5185 |\r\n| nwithin | nwithin | *JSONB\u4e13\u7528* \u4e0d\u88ab\u6307\u5b9ajson\u5305\u542b\u5728\u5185 |\r\n| include | include | *JSONB\u4e13\u7528* \u5305\u542b\u6307\u5b9a\u7684json |\r\n| ninclude | ninclude | *JSONB\u4e13\u7528* \u4e0d\u5305\u542b\u6307\u5b9a\u7684json |\r\n\r\n\r\n\r\n\u5b57\u6bb5\u652f\u6301\uff1a\u666e\u901acolumn\u5b57\u6bb5\u3001relationship\u5b57\u6bb5(single or list)\u3001JSONB[^ 4]\r\n\r\n\u5047\u8bbe\u6709API\u5bf9\u5e94\u5982\u4e0b\u8868\u5b57\u6bb5\r\n\r\n```python\r\nclass User(Base, DictBase):\r\n __tablename__ = 'user'\r\n\r\n id = Column(String(36), primary_key=True)\r\n name = Column(String(36), nullable=False)\r\n department_id = Column(ForeignKey(u'department.id'), nullable=False)\r\n items = Column(JSONB, nullable=False)\r\n\r\n department = relationship(u'Department', lazy=False)\r\n addresses = relationship(u'Address', lazy=False, back_populates=u'user', uselist=True, viewonly=True)\r\n\r\nclass Address(Base, DictBase):\r\n __tablename__ = 'address'\r\n\r\n id = Column(String(36), primary_key=True)\r\n location = Column(String(36), nullable=False)\r\n user_id = Column(ForeignKey(u'user.id'), nullable=False)\r\n items = Column(JSONB, nullable=False)\r\n\r\n user = relationship(u'User', lazy=True)\r\n\r\nclass Department(Base, DictBase):\r\n __tablename__ = 'department'\r\n\r\n id = Column(String(36), primary_key=True)\r\n name = Column(String(36), nullable=False)\r\n user_id = Column(ForeignKey(u'user.id'), nullable=False)\r\n```\r\n\r\n\u53ef\u4ee5\u8fd9\u6837\u6784\u9020\u8fc7\u6ee4\u6761\u4ef6\r\n\r\n/v1/users?name=\u5c0f\u660e\r\n\r\n/v1/users?department.name=\u4e1a\u52a1\u90e8\r\n\r\n/v1/users?addresses.location__icontains=\u5e7f\u4e1c\u7701\r\n\r\n/v1/users?addresses.items.key__icontains=temp\r\n\r\n/v1/users?items.0.age=60 # items = [{\"age\": 60, \"sex\": \"male\"}, {...}]\r\n\r\n/v1/users?items.age=60 # items = {\"age\": 60, \"sex\": \"male\"}\r\n\r\n> v1.2.0 \u8d77\u4e0d\u652f\u6301\u7684column\u6216condition\u4f1a\u89e6\u53d1ResourceBase._unsupported_filter(query, idx, name, op, value)\u51fd\u6570\uff0c\u51fd\u6570\u9ed8\u8ba4\u8fd4\u56de\u53c2\u6570query\u4ee5\u5ffd\u7565\u672a\u652f\u6301\u7684\u8fc7\u6ee4(\u517c\u5bb9\u4ee5\u524d\u7248\u672c\u884c\u4e3a)\uff0c\u7528\u6237\u53ef\u4ee5\u81ea\u884c\u91cd\u8f7d\u51fd\u6570\u4ee5\u5b9e\u73b0\u81ea\u5b9a\u4e49\u884c\u4e3a\r\n>\r\n> v1.2.2 unsupported_filter\u4f1a\u9ed8\u8ba4\u6784\u9020\u4e00\u4e2a\u7a7a\u67e5\u8be2\u96c6\uff0c\u5373\u4e0d\u652f\u6301\u7684column\u6216condition\u4f1a\u81f4\u4f7f\u8fd4\u56de\u7a7a\u7ed3\u679c\r\n\r\n\r\n\r\n\r\n- **\u504f\u79fb\u91cf\u4e0e\u6570\u91cf\u9650\u5236**\r\n\r\n \u67e5\u8be2\u8fd4\u56de\u5217\u8868\u65f6\uff0c\u901a\u5e38\u9700\u8981\u6307\u5b9a\u504f\u79fb\u91cf\u4ee5\u53ca\u6570\u91cf\u9650\u5236\r\n\r\n eg. \r\n\r\n ```bash\r\n __offset=10&__limit=20\r\n ```\r\n\r\n \u4ee3\u8868\u53d6\u504f\u79fb\u91cf10\uff0c\u9650\u523620\u6761\u7ed3\u679c\r\n\r\n- **\u6392\u5e8f**\r\n\r\n \u6392\u5e8f\u5bf9\u67d0\u4e9b\u573a\u666f\u975e\u5e38\u91cd\u8981\uff0c\u53ef\u4ee5\u514d\u53bb\u5ba2\u6237\u7aef\u5f88\u591a\u5de5\u4f5c\u91cf\r\n\r\n ```bash\r\n __orders=name,-env_code\r\n ```\r\n\r\n \u591a\u4e2a\u5b57\u6bb5\u6392\u5e8f\u4ee5\u82f1\u6587\u9017\u53f7\u95f4\u9694\uff0c\u9ed8\u8ba4\u9012\u589e\uff0c\u82e5\u5b57\u6bb5\u524d\u9762\u6709-\u51cf\u53f7\u5219\u4ee3\u8868\u9012\u51cf\r\n\r\n ```\r\n PS\uff1a\u6211\u53ef\u4ee5\u4f7f\u7528+name\u4ee3\u8868\u9012\u589e\u5417\uff1f\r\n\r\n \u53ef\u4ee5\uff0c\u4f46\u662fHTTP URL\u4e2d+\u53f7\u5b9e\u9645\u4e0a\u7684\u7a7a\u683c\u7684\u7f16\u7801\uff0c\u5982\u679c\u4f20\u9012__orders=+name,-env_code\uff0c\u5728HTTP\u4e2d\u5b9e\u9645\u7b49\u4ef7\u4e8e__orders=\u7a7a\u683cname,-env_code, \u65e0\u7b26\u53f7\u9ed8\u8ba4\u9012\u589e\uff0c\u56e0\u6b64\u65e0\u9700\u591a\u4f20\u9012\u4e00\u4e2a+\u53f7\uff0c\u4f20\u9012\u5b57\u6bb5\u5373\u53ef\r\n ```\r\n\r\n- **\u5b57\u6bb5\u9009\u62e9**\r\n\r\n \u63a5\u53e3\u8fd4\u56de\u4e2d\uff0c\u5982\u679c\u5b57\u6bb5\u4fe1\u606f\u592a\u591a\uff0c\u4f1a\u5bfc\u81f4\u4f20\u8f93\u7f13\u6162\uff0c\u5e76\u4e14\u9700\u8981\u5ba2\u6237\u7aef\u5360\u7528\u5927\u91cf\u5185\u5b58\u5904\u7406\r\n\r\n ```bash\r\n __fields=name,env_code\r\n ```\r\n\r\n \u53ef\u4ee5\u6307\u5b9a\u8fd4\u56de\u9700\u8981\u7684\u5b57\u6bb5\u4fe1\u606f\uff0c\u6216\u8005\u5e72\u8106\u4e0d\u6307\u5b9a\uff0c\u83b7\u53d6\u6240\u6709\u670d\u52a1\u5668\u652f\u6301\u7684\u5b57\u6bb5\r\n\r\n\r\n### \u8fdb\u9636\u5f00\u53d1\r\n\r\n#### \u7528\u6237\u8f93\u5165\u6821\u9a8c\r\n\r\n\u7528\u6237\u8f93\u5165\u7684\u6570\u636e\uff0c\u4e0d\u4e00\u5b9a\u662f\u5b8c\u5168\u6b63\u786e\u7684\uff0c\u6bcf\u4e2a\u6570\u636e\u90fd\u9700\u8981\u6821\u9a8c\u540e\u624d\u80fd\u8fdb\u884c\u5b58\u50a8\u548c\u5904\u7406\uff0c\u5728\u4e0a\u9762\u5df2\u7ecf\u63d0\u5230\u8fc7\u4f7f\u7528ColumnValidator\u6765\u8fdb\u884c\u6570\u636e\u6821\u9a8c\uff0c\u8fd9\u91cc\u4e3b\u8981\u662f\u89e3\u91ca\u8be6\u7ec6\u7684\u6821\u9a8c\u89c4\u5219\u548c\u884c\u4e3a\r\n\r\n1. ColumnValidator\u88ab\u9ed8\u8ba4\u96c6\u6210\u5728ResourceBase\u4e2d\uff0c\u6240\u4ee5\u4f1a\u81ea\u52a8\u8fdb\u884c\u6821\u9a8c\u5224\u65ad\r\n\r\n2. \u672a\u5b9a\u4e49_validate\u65f6\uff0c\u5c06\u4e0d\u542f\u7528\u6821\u9a8c\uff0c\u4fe1\u4efb\u6240\u6709\u8f93\u5165\u6570\u636e\r\n\r\n3. \u672a\u5b9a\u4e49\u7684\u5b57\u6bb5\u5728\u6e05\u6d17\u9636\u6bb5\u4f1a\u88ab\u5ffd\u7565\r\n\r\n4. \u6821\u9a8c\u7684\u5173\u952e\u51fd\u6570\u4e3aResourceBase.validate\r\n\r\n ```python\r\n @classmethod\r\n def validate(cls, data, situation, orm_required=False, validate=True, rule=None):\r\n \"\"\"\r\n \u9a8c\u8bc1\u5b57\u6bb5\uff0c\u5e76\u8fd4\u56de\u6e05\u6d17\u540e\u7684\u6570\u636e\r\n\r\n * \u5f53validate=False\uff0c\u4e0d\u4f1a\u5bf9\u6570\u636e\u8fdb\u884c\u6821\u9a8c\uff0c\u4ec5\u8fd4\u56deORM\u9700\u8981\u6570\u636e\r\n * \u5f53validate=True\uff0c\u5bf9\u6570\u636e\u8fdb\u884c\u6821\u9a8c\uff0c\u5e76\u6839\u636eorm_required\u8fd4\u56de\u5168\u90e8/ORM\u6570\u636e\r\n\r\n :param data: \u6e05\u6d17\u524d\u7684\u6570\u636e\r\n :type data: dict\r\n :param situation: \u5f53\u524d\u573a\u666f\r\n :type situation: string\r\n :param orm_required: \u662f\u5426ORM\u9700\u8981\u7684\u6570\u636e(ORM\u5373Model\u8868\u5b9a\u4e49\u7684\u5b57\u6bb5)\r\n :type orm_required: bool\r\n :param validate: \u662f\u5426\u9a8c\u8bc1\u89c4\u5219\r\n :type validate: bool\r\n :param rule: \u89c4\u5219\u914d\u7f6e\r\n :type rule: dict\r\n :returns: \u8fd4\u56de\u6e05\u6d17\u540e\u7684\u6570\u636e\r\n :rtype: dict\r\n \"\"\"\r\n ```\r\n\r\n *validate_on\u4e3a\u4ec0\u4e48\u662f\u586b\u5199\uff1acreate:M\u6216\u8005update:M\uff0c\u56e0\u4e3avalidate\u6309\u7167\u51fd\u6570\u540d\u8fdb\u884c\u573a\u666f\u5224\u5b9a\uff0c\u5728ResourceBase.create\u51fd\u6570\u4e2d\uff0c\u9ed8\u8ba4\u5c06situation\u7ed1\u5b9a\u5728\u5f53\u524d\u51fd\u6570\uff0c\u5373 'create'\uff0cupdate\u540c\u7406\uff0c\u800cM\u4ee3\u8868\u5fc5\u9009\uff0cO\u4ee3\u8868\u53ef\u9009*\r\n\r\n5. \u5f53\u524d\u5feb\u901f\u6821\u9a8c\u89c4\u5219rule_type\u4e0d\u80fd\u6ee1\u8db3\u65f6\uff0c\u8bf7\u4f7f\u7528Validator\u5bf9\u8c61\uff0c\u5185\u7f6eValidator\u5bf9\u8c61\u4e0d\u80fd\u6ee1\u8db3\u9700\u6c42\u65f6\uff0c\u53ef\u4ee5\u5b9a\u5236\u81ea\u5df1\u7684Validator\uff0cValidator\u7684\u5b9a\u4e49\u9700\u8981\u6ee1\u8db32\u70b9\uff1a\r\n\r\n \u4eceNullValidator\u4e2d\u7ee7\u627f\r\n\r\n \u91cd\u5199validate\u51fd\u6570\uff0c\u51fd\u6570\u63a5\u53d7\u4e00\u4e2a\u53c2\u6570\uff0c\u5e76\u4e14\u8fd4\u56deTrue\u4f5c\u4e3a\u901a\u8fc7\u6821\u9a8c\uff0c\u8fd4\u56de\u9519\u8bef\u5b57\u7b26\u4e32\u4ee3\u8868\u6821\u9a8c\u5931\u8d25\r\n\r\n6. Converter\u540c\u4e0a\r\n\r\n#### \u9ad8\u7ea7DB\u64cd\u4f5c[hooks, \u81ea\u5b9a\u4e49query]\r\n\r\n##### \u7b80\u5355hooks\r\n\r\n\u5728db\u521b\u5efa\u4e00\u4e2a\u8bb0\u5f55\u65f6\uff0c\u5047\u8bbe\u5e0c\u671bid\u662f\u81ea\u52a8\u751f\u6210\u7684UUID\uff0c\u901a\u5e38\u8fd9\u610f\u5473\u7740\u6211\u4eec\u4e0d\u5f97\u4e0d\u91cd\u5199create\u51fd\u6570\uff1a\r\n\r\n```python\r\nclass User(ResourceBase):\r\n orm_meta = models.User\r\n _primary_keys = 'id'\r\n\r\n def create(self, resource, validate=True, detail=True):\r\n resource['id'] = uuid.uuid4().hex\r\n super(User, self).create(resource, validate=validate, detail=validate)\r\n```\r\n\r\n\u8fd9\u6837\u7684\u64cd\u4f5c\u5bf9\u4e8e\u6211\u4eec\u800c\u8a00\u662f\u5f88\u7b28\u91cd\u7684\uff0c\u751a\u81f3create\u7684\u5b9e\u73b0\u6bd4\u8f83\u590d\u6742\uff0c\u8ba9\u6211\u4eec\u4e0d\u5e0c\u671b\u5230create\u91cc\u9762\u53bb\u52a0\u8fd9\u4e9b\u4e0d\u662f\u90a3\u4e48\u5173\u952e\u7684\u4ee3\u7801\uff0c\u5bf9\u4e8e\u8fd9\u4e9b\u64cd\u4f5c\uff0ctalos\u5206\u6210\u4e862\u79cd\u573a\u666f\uff0c_before_create, _addtional_create\uff0c\u6839\u636e\u540d\u79f0\u6211\u4eec\u80fd\u77e5\u9053\uff0c\u5b83\u4eec\u5206\u522b\u4ee3\u8868\r\n\r\ncreate\u6267\u884c\u5f00\u59cb\u524d\uff1a\u5e38\u7528\u4e8e\u4e00\u4e9b\u6570\u636e\u7684\u81ea\u52a8\u586b\u5145\r\n\r\ncreate\u6267\u884c\u540e\u4f46\u672a\u63d0\u4ea4\uff1a\u5e38\u7528\u4e8e\u5f3a\u4e8b\u52a1\u63a7\u5236\u7684\u64cd\u4f5c\uff0c\u53ef\u4ee5\u4f7f\u7528\u540c\u4e00\u4e2a\u4e8b\u52a1\u8fdb\u884c\u64cd\u4f5c\u4ee5\u4fbf\u4e00\u8d77\u63d0\u4ea4\u6216\u56de\u6eda\r\n\r\n\u540c\u7406\u8fd8\u6709update\uff0cdelete\r\n\r\n\u540c\u6837\u7684list\u548ccount\u90fd\u6709_addtional_xxxx\u94a9\u5b50\r\n\r\n##### \u52a8\u6001hooks\r\n\r\n\u4ee5\u4e0a\u7684hooks\u90fd\u662f\u7c7b\u6210\u5458\u51fd\u6570\u4ee3\u7801\u5b9a\u4e49\u7684\uff0c\u5f53\u4f7f\u7528\u8005\u60f3\u8981\u4e34\u65f6\u589e\u52a0\u4e00\u4e2ahook\u7684\u65f6\u5019\u5462\uff0c\u6216\u8005\u6839\u636e\u67d0\u4e2a\u6761\u4ef6\u5224\u65ad\u662f\u5426\u4f7f\u7528\u4e00\u4e2ahook\u65f6\uff0c\u6211\u4eec\u9700\u8981\u4e00\u79cd\u66f4\u52a8\u6001\u7684hook\u6765\u652f\u6301\uff0c\u76ee\u524d\u53ea\u6709list\u548ccount\u652f\u6301\u6b64\u7c7bhooks\r\n\r\nlist,count\u7684hook\u7684\u51fd\u6570\u5b9a\u4e49\u4e3a\uff1afunction(query, filters)\uff0c\u9700\u8981return \u5904\u7406\u540e\u7684query\r\n\r\neg. self.list(hooks=[lambda q,f: return q])\r\n\r\n##### \u81ea\u5b9a\u4e49query\r\n\r\n\u5728\u66f4\u590d\u6742\u7684\u573a\u666f\u4e0b\u6211\u4eec\u5c01\u88c5\u7684\u64cd\u4f5c\u51fd\u6570\u53ef\u80fd\u65e0\u6cd5\u8fbe\u5230\u76ee\u7684\uff0c\u6b64\u65f6\u53ef\u4ee5\u4f7f\u7528\u5e95\u5c42\u7684SQLAlchemy Query\u5bf9\u8c61\u6765\u8fdb\u884c\u5904\u7406\uff0c\u6bd4\u5982\u5728PG\u4e2dINET\u7c7b\u578b\u7684\u6bd4\u8f83\u64cd\u4f5c\uff1a\r\n\r\n\u4e00\u4e2a\u573a\u666f\uff1a\u6211\u4eec\u4e0d\u5e0c\u671b\u7528\u6237\u65b0\u589e\u7684\u5b50\u7f51\u4fe1\u606f\u4e0e\u73b0\u6709\u5b50\u7f51\u91cd\u53e0\r\n\r\n```python\r\nquery = self._get_query(session)\r\nquery = query.filter(self.orm_meta.cidr.op(\">>\")(\r\n subnet['cidr']) | self.orm_meta.cidr.op(\"<<\")(subnet['cidr']))\r\nif query.one_or_none():\r\n raise ConflictError()\r\n```\r\n\r\n#### \u4f1a\u8bdd\u91cd\u7528\u548c\u4e8b\u52a1\u63a7\u5236\r\n\r\n\u5728talos\u4e2d\uff0c\u6bcf\u4e2aResourceBase\u5bf9\u8c61\u90fd\u53ef\u4ee5\u7533\u8bf7\u4f1a\u8bdd\u548c\u4e8b\u52a1\uff0c\u800c\u4e14\u53ef\u4ee5\u63a5\u53d7\u4e00\u4e2a\u5df2\u6709\u7684\u4f1a\u8bdd\u548c\u4e8b\u52a1\u5bf9\u8c61\uff0c\u5728\u4f7f\u7528\u5b8c\u6bd5\u540etalos\u4f1a\u81ea\u52a8\u5e2e\u52a9\u4f60\u8fdb\u884c\u56de\u6eda/\u63d0\u4ea4/\u5173\u95ed\uff0c\u8fd9\u5f97\u76ca\u4e0epython\u7684with\u5b50\u53e5\r\n\r\n```python\r\nu = User()\r\nwith u.transaction() as session:\r\n u.update(...)\r\n # \u4e8b\u52a1\u91cd\u7528, \u53ef\u4ee5\u67e5\u8be2\u548c\u53d8\u66f4\u64cd\u4f5c, with\u5b50\u53e5\u7ed3\u675f\u4f1a\u81ea\u52a8\u63d0\u4ea4\uff0c\u5f02\u5e38\u4f1a\u81ea\u52a8\u56de\u6eda\r\n UserPhone(transaction=session).delete(...)\r\n UserPhone(transaction=session).list(...)\r\nwith u.get_session() as session:\r\n # \u4f1a\u8bdd\u91cd\u7528, \u53ef\u4ee5\u67e5\u8be2\r\n UserPhone(session=session).list(...)\r\n```\r\n\r\n#### \u7f13\u5b58\r\n\r\n##### \u914d\u7f6e\u548c\u4f7f\u7528\r\n\r\n\u9ed8\u8ba4\u914d\u7f6e\u4e3a\u8fdb\u7a0b\u5185\u5b58\uff0c\u8d85\u65f660\u79d2\r\n\r\n```python\r\n'cache': {\r\n 'type': 'dogpile.cache.memory',\r\n 'expiration_time': 60\r\n}\r\n```\r\n\r\n\u7f13\u5b58\u540e\u7aef\u652f\u6301\u53d6\u51b3\u4e8edogpile\u6a21\u5757\uff0c\u53ef\u4ee5\u652f\u6301\u5e38\u89c1\u7684memcache\uff0credis\u7b49\r\n\r\n\u5982\uff1aredis\r\n\r\n```python\r\n\"cache\": {\r\n \"type\": \"dogpile.cache.redis\",\r\n \"expiration_time\": 6,\r\n \"arguments\": {\r\n \"host\": \"127.0.0.1\",\r\n \"password\": \"football\",\r\n \"port\": 1234,\r\n \"db\": 0,\r\n \"redis_expiration_time\": 60,\r\n \"distributed_lock\": true\r\n }\r\n }\r\n```\r\n\r\n\u4f7f\u7528\u65b9\u5f0f\r\n\r\n```python\r\nfrom talos.common import cache\r\n\r\ncache.get(key, exipres=None)\r\ncache.set(key, value)\r\ncache.validate(value)\r\ncache.get_or_create(key, creator, expires=None)\r\ncache.delete(key)\r\n```\r\n\r\n\r\n\r\n#### \u5f02\u6b65\u4efb\u52a1\r\n\r\n##### \u5b9a\u4e49\u5f02\u6b65\u4efb\u52a1\r\n\r\n\u5efa\u7acbworkers.app_name.task.py\u7528\u4e8e\u7f16\u5199\u8fdc\u7a0b\u4efb\u52a1\r\n\u5efa\u7acbworkers.app_name.callback.py\u7528\u4e8e\u7f16\u5199\u8fdc\u7a0b\u56de\u8c03\r\ntask.py\u4efb\u52a1\u793a\u4f8b\r\n\r\n```python\r\nfrom talos.common import celery\r\nfrom talos.common import async_helper\r\nfrom cms.workers.app_name import callback\r\n@celery.app.task\r\ndef add(task_id, x, y):\r\n result = x + y\r\n # \u8fd9\u91cc\u8fd8\u53ef\u4ee5\u901a\u77e5\u5176\u4ed6\u9644\u52a0\u4efb\u52a1\r\n async_helper.send_task('cms.workers.app_name.tasks.other_task', kwargs={'result': result, 'task_id': task_id})\r\n # send callback\u7684\u53c2\u6570\u5fc5\u987b\u4e0ecallback\u51fd\u6570\u53c2\u6570\u5339\u914d(request\uff0cresponse\u9664\u5916)\r\n async_helper.send_callback(url_base, callback.callback_add,\r\n data,\r\n task_id=task_id)\r\n # \u6b64\u5904\u662f\u5f02\u6b65\u56de\u8c03\u7ed3\u679c\uff0c\u4e0d\u9700\u8981\u670d\u52a1\u5668\u7b49\u5f85\u6216\u8005\u8f6e\u8be2\uff0cworker\u4f1a\u4e3b\u52a8\u53d1\u9001\u8fdb\u5ea6\u6216\u8005\u7ed3\u679c\uff0c\u53ef\u4ee5\u4e0dreturn\r\n # \u5982\u679c\u60f3\u8981\u4f7f\u7528return\u65b9\u5f0f\uff0c\u5219\u6309\u7167\u6b63\u5e38celery\u6d41\u7a0b\u7f16\u5199\u4ee3\u7801\r\n return result\r\n```\r\n\r\ncallback.py\u56de\u8c03\u793a\u4f8b\r\n```python\r\nfrom talos.common import async_helper\r\n# \u53ef\u4ee5\u4f7f\u7528\u51fd\u6570\u53c2\u6570\u4e2d\u7684\u4efb\u610f\u53d8\u91cf\u4f5c\u4e3aurl\u7684\u53d8\u91cf\uff08\u4e3a\u4e86\u67d0\u79cd\u60c5\u51b5\u4e0b\u4f5c\u4e3aurl\u533a\u5206\uff09\uff0c\u5f53\u7136\u4e5f\u53ef\u4ee5\u4e0d\u7528\r\n@async_helper.callback('/callback/add/{task_id}')\r\ndef callback_add(data, task_id, request=None, response=None):\r\n db.save(task_id, result)\r\n```\r\n\r\nroute\u4e2d\u6dfb\u52a0\u56de\u8c03\r\n```python\r\nfrom talos.common import async_helper\r\nfrom project_name.workers.app_name import callback\r\n\r\ndef add_route(api):\r\n async_helper.add_callback_route(api, callback.callback_add)\r\n```\r\n\r\n\u542f\u52a8worker\r\n celery -A cms.server.celery_worker worker --autoscale=50,4 --loglevel=DEBUG -Q your_queue_name\r\n\r\n\u8c03\u7528\r\n add('id', 1, 1).delay()\r\n \u4f1a\u6709\u4efb\u52a1\u53d1\u9001\u5230worker\u4e2d\uff0c\u7136\u540ewoker\u4f1a\u542f\u52a8\u4e00\u4e2aother_task\u4efb\u52a1\uff0c\u5e76\u56de\u8c03url\u5c06\u7ed3\u679c\u53d1\u9001\u4f1a\u670d\u52a1\u7aef\r\n\r\n##### \u5f02\u6b65\u4efb\u52a1\u914d\u7f6e\r\n\r\n\u4f9d\u8d56\uff1a\r\n\u200b \u5e93\uff1a\r\n\u200b celery\r\n\r\n\u200b \u914d\u7f6e\uff1a\r\n\r\n```\r\n{\r\n ...\r\n \"celery\": {\r\n \"worker_concurrency\": 8,\r\n \"broker_url\": \"pyamqp://guest@127.0.0.1//\",\r\n \"result_backend\": \"redis://127.0.0.1\",\r\n \"imports\": [\r\n \"project_name.workers.app_name.tasks\"\r\n ],\r\n \"task_serializer\": \"json\",\r\n \"result_serializer\": \"json\",\r\n \"accept_content\": [\"json\"],\r\n \"worker_prefetch_multiplier\": 1,\r\n \"task_routes\": {\r\n \"project_name.workers.*\": {\"queue\": \"your_queue_name\",\r\n \"exchange\": \"your_exchange_name\",\r\n \"routing_key\": \"your_routing_name\"}\r\n }\r\n },\r\n \"worker\": {\r\n \"callback\": {\r\n \"strict_client\": true,\r\n \"allow_hosts\": [\"127.0.0.1\"]\r\n }\r\n }\r\n}\r\n```\r\n\r\n\r\n\r\n#### \u5b9a\u65f6\u4efb\u52a1[^ 2]\r\n\r\ntalos\u4e2d\u4f60\u53ef\u4ee5\u4f7f\u7528\u539f\u751fcelery\u7684\u5b9a\u65f6\u4efb\u52a1\u673a\u5236\uff0c\u4e5f\u53ef\u4ee5\u4f7f\u7528talos\u4e2d\u63d0\u4f9b\u7684\u6269\u5c55\u5b9a\u65f6\u4efb\u52a1(TScheduler)\uff0c\u6269\u5c55\u7684\u5b9a\u65f6\u4efb\u52a1\u53ef\u4ee5\u57285s(\u53ef\u901a\u8fc7beat_max_loop_interval\u6765\u4fee\u6539\u8fd9\u4e2a\u65f6\u95f4)\u5185\u53d1\u73b0\u5b9a\u65f6\u4efb\u52a1\u7684\u53d8\u5316\u5e76\u5237\u65b0\u8c03\u5ea6\uff0c\u4ece\u800c\u63d0\u4f9b\u52a8\u6001\u7684\u5b9a\u65f6\u4efb\u52a1\uff0c\u800c\u5b9a\u65f6\u4efb\u52a1\u7684\u6765\u6e90\u53ef\u4ee5\u4ece\u914d\u7f6e\u6587\u4ef6\uff0c\u4e5f\u53ef\u4ee5\u901a\u8fc7\u81ea\u5b9a\u4e49\u7684\u51fd\u6570\u4e2d\u52a8\u6001\u63d0\u4f9b\r\n\r\n> \u539f\u751fcelery\u7684scheduler\u662f\u4e0d\u652f\u6301\u52a8\u6001\u5b9a\u65f6\u4efb\u52a1\u7684\r\n\r\n> \u4f7f\u7528\u539f\u751fcelery\u5b9a\u65f6\u4efb\u52a1\u56e0\u4e3atalos\u914d\u7f6e\u9879\u4e3ajson\u6570\u636e\u800c\u65e0\u6cd5\u63d0\u4f9b\u590d\u6742\u7c7b\u578b\u7684schedule\uff0c\u5f53\u7136\u4e5f\u53ef\u4ee5\u4f7f\u7528add_periodic_task\u6765\u89e3\u51b3\uff0c\u4f46\u4f1a\u964d\u4f4e\u6211\u4eec\u4f7f\u7528\u7684\u4fbf\u5229\u6027\r\n>\r\n> \u8fd9\u4e9b\u95ee\u9898\u5728talos\u6269\u5c55\u5b9a\u65f6\u4efb\u52a1\u4e2d\u5f97\u4ee5\u89e3\u51b3\r\n\r\n##### \u9759\u6001\u914d\u7f6e\u5b9a\u65f6\u4efb\u52a1\uff1a\r\n\r\n\u4f7f\u7528\u6700\u539f\u59cb\u7684celery\u5b9a\u65f6\u4efb\u52a1\u914d\u7f6e\uff0c\u6700\u5feb\u6377\u7684\u5b9a\u65f6\u4efb\u52a1\u4f8b\u5b50[^ 3]\uff1a\r\n\r\n```json\r\n \"celery\": {\r\n \"worker_concurrency\": 8,\r\n \"broker_url\": \"pyamqp://test:test@127.0.0.1//\",\r\n ...\r\n \"beat_schedule\": {\r\n \"test_every_5s\": {\r\n \"task\": \"cms.workers.periodic.tasks.test_add\",\r\n \"schedule\": 5,\r\n \"args\": [3,6] \r\n }\r\n }\r\n```\r\n\r\n\u542f\u52a8beat\uff1a celery -A cms.server.celery_worker beat --loglevel=DEBUG\r\n\r\n\u542f\u52a8worker\uff1acelery -A cms.server.celery_worker worker --loglevel=DEBUG -Q cms-dev-queue\r\n\r\n\u53ef\u4ee5\u770b\u5230\u6bcf5s\uff0cbeat\u4f1a\u53d1\u9001\u4e00\u4e2a\u4efb\u52a1\uff0cworker\u4f1a\u63a5\u6536\u6b64\u4efb\u52a1\u8fdb\u884c\u5904\u7406\uff0c\u4ece\u800c\u5f62\u6210\u5b9a\u65f6\u4efb\u52a1\r\n\r\n\u4f7f\u7528\u8fc7\u539f\u751fcelery\u7684\u4eba\u53ef\u80fd\u770b\u51fa\u8fd9\u91cc\u5b58\u5728\u7684\u95ee\u9898\uff1acrontab\u662f\u5bf9\u8c61\uff0cjson\u914d\u7f6e\u662f\u65e0\u6cd5\u4f20\u9012\uff0c\u53ea\u80fd\u914d\u7f6e\u7b80\u5355\u7684\u95f4\u9694\u4efb\u52a1\uff0c\u786e\u5b9e\uff0c\u7f3a\u7701\u60c5\u51b5\u4e0b\u7531\u4e8e\u914d\u7f6e\u6587\u4ef6\u683c\u5f0f\u7684\u539f\u56e0\u65e0\u6cd5\u63d0\u4f9b\u66f4\u9ad8\u7ea7\u7684\u5b9a\u65f6\u4efb\u52a1\u914d\u7f6e\uff0c\u6240\u4ee5talos\u63d0\u4f9b\u4e86\u81ea\u5b9a\u4e49\u7684Scheduler\uff1aTScheduler\uff0c\u8fd9\u4e2a\u8c03\u5ea6\u5668\u53ef\u4ee5\u4ece\u914d\u7f6e\u6587\u4ef6\u4e2d\u89e3\u6790interval\u3001crontab\u7c7b\u578b\u7684\u5b9a\u65f6\u4efb\u52a1\uff0c\u4ece\u800c\u8986\u76d6\u66f4\u5e7f\u6cdb\u7684\u9700\u6c42\uff0c\u800c\u4f7f\u7528\u4e5f\u975e\u5e38\u7b80\u5355\uff1a\r\n\r\n```json\r\n\"celery\": {\r\n \"worker_concurrency\": 8,\r\n \"broker_url\": \"pyamqp://test:test@127.0.0.1//\",\r\n ...\r\n \"beat_schedule\": {\r\n \"test_every_5s\": {\r\n \"task\": \"cms.workers.periodic.tasks.test_add\",\r\n \"schedule\": 5,\r\n \"args\": [3,6] \r\n },\r\n \"test_every_123s\": {\r\n \"type\": \"interval\",\r\n \"task\": \"cms.workers.periodic.tasks.test_add\",\r\n \"schedule\": \"12.3\",\r\n \"args\": [3,6] \r\n },\r\n \"test_crontab_simple\": {\r\n \"type\": \"crontab\",\r\n \"task\": \"cms.workers.periodic.tasks.test_add\",\r\n \"schedule\": \"*/1\",\r\n \"args\": [3,6] \r\n },\r\n \"test_crontab\": {\r\n \"type\": \"crontab\",\r\n \"task\": \"cms.workers.periodic.tasks.test_add\",\r\n \"schedule\": \"1,13,30-45,50-59/2 *1 * * *\",\r\n \"args\": [3,6] \r\n }\r\n }\r\n```\r\n\u4f9d\u7136\u662f\u5728\u914d\u7f6e\u6587\u4ef6\u4e2d\u5b9a\u4e49\uff0c\u591a\u4e86\u4e00\u4e2atype\u53c2\u6570\uff0c\u7528\u4e8e\u5e2e\u52a9\u8c03\u5ea6\u5668\u89e3\u6790\u5b9a\u65f6\u4efb\u52a1\uff0c\u6b64\u5916\u8fd8\u9700\u8981\u6307\u5b9a\u4f7f\u7528talos\u7684TScheduler\u8c03\u5ea6\u5668\uff0c\u6bd4\u5982\u914d\u7f6e\u4e2d\u6307\u5b9a:\r\n\r\n```\r\n\"celery\": {\r\n \"worker_concurrency\": 8,\r\n \"broker_url\": \"pyamqp://test:test@127.0.0.1//\",\r\n ...\r\n \"beat_schedule\": {...}\r\n \"beat_scheduler\": \"talos.common.scheduler:TScheduler\"\r\n```\r\n\r\n\u6216\u8005\u547d\u4ee4\u884c\u542f\u52a8\u65f6\u6307\u5b9a\uff1a\r\n\r\n\u542f\u52a8beat\uff1a celery -A cms.server.celery_worker beat --loglevel=DEBUG -S talos.common.scheduler:TScheduler \r\n\r\n\u542f\u52a8worker\uff1acelery -A cms.server.celery_worker worker --loglevel=DEBUG -Q cms-dev-queue\r\n\r\n\u9664\u4e86type\uff0cTScheduler\u7684\u4efb\u52a1\u8fd8\u63d0\u4f9b\u4e86\u5f88\u591a\u5176\u4ed6\u7684\u6269\u5c55\u5c5e\u6027\uff0c\u4ee5\u4e0b\u662f\u5c5e\u6027\u4ee5\u53ca\u5176\u63cf\u8ff0\r\n\r\n```\r\nname: string, \u552f\u4e00\u540d\u79f0\r\ntask: string, \u4efb\u52a1\u6a21\u5757\u51fd\u6570\r\n[description]: string, \u5907\u6ce8\u4fe1\u606f\r\n[type]: string, interval \u6216 crontab, \u9ed8\u8ba4 interval\r\nschedule: string/int/float/schedule eg. 1.0,'5.1', '10 *' , '*/10 * * * *' \r\nargs: tuple/list, \u53c2\u6570\r\nkwargs: dict, \u547d\u540d\u53c2\u6570\r\n[priority]: int, \u4f18\u5148\u7ea7, \u9ed8\u8ba45\r\n[expires]: int, \u5355\u4f4d\u4e3a\u79d2\uff0c\u5f53\u4efb\u52a1\u4ea7\u751f\u540e\uff0c\u591a\u4e45\u8fd8\u6ca1\u88ab\u6267\u884c\u4f1a\u8ba4\u4e3a\u8d85\u65f6\r\n[enabled]: bool, True/False, \u9ed8\u8ba4True\r\n[max_calls]: None/int, \u6700\u5927\u8c03\u5ea6\u6b21\u6570, \u9ed8\u8ba4None\u65e0\u9650\u5236\r\n[last_updated]: Datetime, \u4efb\u52a1\u6700\u540e\u66f4\u65b0\u65f6\u95f4\uff0c\u5e38\u7528\u4e8e\u5224\u65ad\u662f\u5426\u6709\u5b9a\u65f6\u4efb\u52a1\u9700\u8981\u66f4\u65b0\r\n```\r\n\r\n\r\n\r\n##### \u52a8\u6001\u914d\u7f6e\u5b9a\u65f6\u4efb\u52a1\uff1a\r\n\r\nTScheduler\u7684\u52a8\u6001\u4efb\u52a1\u4ec5\u9650\u7528\u6237\u81ea\u5b9a\u4e49\u7684\u6240\u6709schedules\r\n\u6240\u6709\u5b9a\u65f6\u4efb\u52a1 = \u914d\u7f6e\u6587\u4ef6\u4efb\u52a1 + add_periodic_task\u4efb\u52a1 + hooks\u4efb\u52a1\uff0chooks\u4efb\u52a1\u53ef\u4ee5\u901a\u8fc7\u76f8\u540cname\u6765\u8986\u76d6\u5df2\u5b58\u5728\u914d\u7f6e\u4e2d\u7684\u4efb\u52a1\uff0c\u5426\u5219\u76f8\u4e92\u72ec\u7acb\r\n\r\n- \u4f7f\u7528TScheduler\u9884\u7559\u7684hooks\u8fdb\u884c\u52a8\u6001\u5b9a\u65f6\u4efb\u52a1\u914d\u7f6e(\u63a8\u8350\u65b9\u5f0f)\uff1a\r\n\r\n TScheduler\u4e2d\u9884\u7559\u4e862\u4e2ahooks\uff1atalos_on_user_schedules_changed/talos_on_user_schedules\r\n\r\n **talos_on_user_schedules_changed**\u94a9\u5b50\u7528\u4e8e\u5224\u65ad\u662f\u5426\u9700\u8981\u66f4\u65b0\u5b9a\u65f6\u5668\uff0c\u94a9\u5b50\u88ab\u6267\u884c\u7684\u6700\u5c0f\u95f4\u9694\u662fbeat_max_loop_interval(\u5982\u4e0d\u8bbe\u7f6e\u9ed8\u8ba4\u4e3a5s)\r\n\r\n \u94a9\u5b50\u5b9a\u4e49\u4e3acallable(scheduler)\uff0c\u8fd4\u56de\u503c\u662fTrue/False\r\n\r\n **talos_on_user_schedules**\u94a9\u5b50\u7528\u4e8e\u63d0\u4f9b\u65b0\u7684\u5b9a\u65f6\u5668\u5b57\u5178\u6570\u636e\r\n\r\n \u94a9\u5b50\u5b9a\u4e49\u4e3acallable(scheduler)\uff0c\u8fd4\u56de\u503c\u662f\u5b57\u5178\uff0c\u5168\u91cf\u7684\u81ea\u5b9a\u4e49\u52a8\u6001\u5b9a\u65f6\u5668\r\n\r\n \u6211\u4eec\u6765\u5c1d\u8bd5\u63d0\u4f9b\u4e00\u4e2a\uff0c\u6bcf\u8fc713\u79d2\u81ea\u52a8\u751f\u6210\u4e00\u4e2a\u5168\u65b0\u5b9a\u65f6\u5668\u7684\u4ee3\u7801\r\n\r\n \u4ee5\u4e0b\u662fcms.workers.periodic.hooks.py\u7684\u6587\u4ef6\u5185\u5bb9\r\n\r\n ```python\r\n import datetime\r\n from datetime import timedelta\r\n import random\r\n\r\n # talos_on_user_schedules_changed, \u7528\u4e8e\u5224\u65ad\u662f\u5426\u9700\u8981\u66f4\u65b0\u5b9a\u65f6\u5668\r\n # \u9ed8\u8ba4\u6bcf5s\u8c03\u7528\u4e00\u6b21\r\n class ChangeDetection(object):\r\n '''\r\n \u7b49\u4ef7\u4e8e\u51fd\u6570\uff0c\u53ea\u662f\u6b64\u5904\u6211\u4eec\u9700\u8981\u4fdd\u7559_last_modify\u5c5e\u6027\u6240\u4ee5\u7528\u7c7b\u6765\u5b9a\u4e49callable\r\n def ChangeDetection(scheduler):\r\n ...do something...\r\n '''\r\n def __init__(self, scheduler):\r\n self._last_modify = self.now()\r\n def now(self):\r\n return datetime.datetime.now()\r\n def __call__(self, scheduler):\r\n now = self.now()\r\n # \u6bcf\u8fc713\u79d2\u5b9a\u4e49\u5b9a\u65f6\u5668\u6709\u66f4\u65b0\r\n if now - self._last_modify >= timedelta(seconds=13):\r\n self._last_modify = now\r\n return True\r\n return False\r\n\r\n # talos_on_user_schedules, \u7528\u4e8e\u63d0\u4f9b\u65b0\u7684\u5b9a\u65f6\u5668\u5b57\u5178\u6570\u636e\r\n # \u5728talos_on_user_schedules_changed hooks\u8fd4\u56deTrue\u540e\u88ab\u8c03\u7528\r\n class Schedules(object):\r\n '''\r\n \u7b49\u4ef7\u4e8e\u51fd\u6570\r\n def Schedules(scheduler):\r\n ...do something...\r\n '''\r\n def __init__(self, scheduler):\r\n pass\r\n def __call__(self, scheduler):\r\n interval = random.randint(1,10)\r\n name = 'dynamic_every_%s s' % interval\r\n # \u751f\u6210\u4e00\u4e2a\u7eaf\u968f\u673a\u7684\u5b9a\u65f6\u4efb\u52a1\r\n return {name: {'task': 'cms.workers.periodic.tasks.test_add', 'schedule': interval, 'args': (1,3)}}\r\n ```\r\n\r\n \u914d\u7f6e\u6587\u4ef6\u5982\u4e0b\uff1a\r\n\r\n ```json\r\n \"celery\": {\r\n ...\r\n \"beat_schedule\": {\r\n \"every_5s_max_call_2_times\": {\r\n \"task\": \"cms.workers.periodic.tasks.test_add\",\r\n \"schedule\": \"5\",\r\n \"max_calls\": 2,\r\n \"enabled\": true,\r\n \"args\": [1, 3]\r\n }\r\n },\r\n \"talos_on_user_schedules_changed\":[\r\n \"cms.workers.periodic.hooks:ChangeDetection\"],\r\n \"talos_on_user_schedules\": [\r\n \"cms.workers.periodic.hooks:Schedules\"]\r\n },\r\n ```\r\n\r\n \u5f97\u5230\u7684\u7ed3\u679c\u662f\uff0c\u4e00\u4e2a\u6bcf5s\uff0c\u6700\u591a\u8c03\u5ea62\u6b21\u7684\u5b9a\u65f6\u4efb\u52a1\uff1b\u4e00\u4e2a\u6bcf>=13s\u81ea\u52a8\u751f\u6210\u7684\u968f\u673a\u5b9a\u65f6\u4efb\u52a1\r\n\r\n- \u4f7f\u7528\u5b98\u65b9\u7684setup_periodic_tasks\u8fdb\u884c\u52a8\u6001\u914d\u7f6e\r\n\r\n \u89c1celery\u6587\u6863\r\n\r\n \u622a\u6b62\u5f53\u524d2018.11.13 celery 4.2.0\u5728\u5b9a\u65f6\u4efb\u52a1\u4e2d\u4f9d\u7136\u5b58\u5728\u95ee\u9898\uff0c\u4f7f\u7528\u5b98\u65b9\u5efa\u8bae\u7684on_after_configure\u52a8\u6001\u914d\u7f6e\u5b9a\u65f6\u5668\u65f6\uff0c\u5b9a\u65f6\u4efb\u52a1\u4e0d\u4f1a\u88ab\u89e6\u53d1\uff1a[GitHub Issue 3589](https://github.com/celery/celery/issues/3589)\r\n\r\n ```\r\n @celery.app.on_after_configure.connect\r\n def setup_periodic_tasks(sender, **kwargs):\r\n sender.add_periodic_task(3.0, test.s('add every 3s by add_periodic_task'), name='add every 3s by add_periodic_task')\r\n\r\n @celery.app.task\r\n def test(arg):\r\n print(arg)\r\n ```\r\n\r\n\u800c\u6d4b\u8bd5\u4ee5\u4e0b\u4ee3\u7801\u6709\u6548\uff0c\u53ef\u4ee5\u4f7f\u7528\u4ee5\u4e0b\u65b9\u6cd5\uff1a\r\n\r\n```\r\n@celery.app.on_after_finalize.connect\r\ndef setup_periodic_tasks(sender, **kwargs):\r\n sender.add_periodic_task(3.0, test.s('add every 3s by add_periodic_task'), name='add every 3s by add_periodic_task')\r\n\r\n@celery.app.task\r\ndef test(arg):\r\n print(arg)\r\n```\r\n\r\n#### \u9891\u7387\u9650\u5236\r\n\r\n##### controller & \u4e2d\u95f4\u4ef6 \u9891\u7387\u9650\u5236\r\n\r\n\u4e3b\u8981\u7528\u4e8ehttp\u63a5\u53e3\u9891\u7387\u9650\u5236\r\n\r\n \u57fa\u672c\u4f7f\u7528\u6b65\u9aa4\uff1a\r\n\r\n - \u5728controller\u4e0a\u914d\u7f6e\u88c5\u9970\u5668\r\n - \u5c06Limiter\u914d\u7f6e\u5230\u542f\u52a8\u4e2d\u95f4\u4ef6\r\n\r\n \u88c5\u9970\u5668\u901a\u8fc7\u7ba1\u7406\u6620\u5c04\u5173\u7cfb\u8868LIMITEDS\uff0cLIMITEDS_EXEMPT\u6765\u5b9a\u4f4d\u7528\u6237\u8bbe\u7f6e\u7684\u7c7b\u5b9e\u4f8b->\u9891\u7387\u9650\u5236\u5668\u5173\u7cfb\uff0c\r\n \u9891\u7387\u9650\u5236\u5668\u662f\u5b9e\u529b\u7ea7\u522b\u7684\uff0c\u610f\u5473\u7740\u6bcf\u4e2a\u5b9e\u4f8b\u90fd\u4f7f\u7528\u81ea\u5df1\u7684\u9891\u7387\u9650\u5236\u5668\r\n\r\n \u9891\u7387\u9650\u5236\u5668\u67097\u4e2a\u4e3b\u8981\u53c2\u6570\uff1a\u9891\u7387\u8bbe\u7f6e\uff0c\u5173\u952e\u9650\u5236\u53c2\u6570\uff0c\u9650\u5236\u8303\u56f4\uff0c\u662f\u5426\u5bf9\u72ec\u7acb\u65b9\u6cd5\u8fdb\u884c\u4e0d\u540c\u9650\u5236, \u7b97\u6cd5\uff0c\u9519\u8bef\u63d0\u793a\u4fe1\u606f, hit\u51fd\u6570\r\n\r\n - \u9891\u7387\u8bbe\u7f6e\uff1a\u683c\u5f0f[count] [per|/] [n (optional)] [second|minute|hour|day|month|year]\r\n - \u5173\u952e\u9650\u5236\u53c2\u6570\uff1a\u9ed8\u8ba4\u4e3aIP\u5730\u5740(\u652f\u6301X-Forwarded-For)\uff0c\u81ea\u5b9a\u4e49\u51fd\u6570\uff1adef key_func(request) -> string\r\n - \u9650\u5236\u8303\u56f4\uff1a\u9ed8\u8ba4python\u7c7b\u5b8c\u6574\u8def\u5f84\uff0c\u81ea\u5b9a\u4e49\u51fd\u6570def scope_func(request) -> string\r\n - \u662f\u5426\u5bf9\u72ec\u7acb\u65b9\u6cd5\u8fdb\u884c\u4e0d\u540c\u9650\u5236: \u5e03\u5c14\u503c\uff0c\u9ed8\u8ba4True\r\n - \u7b97\u6cd5\uff1a\u652f\u6301fixed-window\u3001fixed-window-elastic-expiry\u3001moving-window\r\n - \u9519\u8bef\u63d0\u793a\u4fe1\u606f\uff1a\u9519\u8bef\u63d0\u793a\u4fe1\u606f\u53ef\u63a5\u53d73\u4e2a\u683c\u5f0f\u5316\uff08limit\uff0cremaining\uff0creset\uff09\u5185\u5bb9\r\n - hit\u51fd\u6570\uff1a\u51fd\u6570\u5b9a\u4e49\u4e3adef hit(resource, request) -> bool\uff0c\u4e3aTrue\u65f6\u5219\u89e6\u53d1\u9891\u7387\u9650\u5236\u5668hit\uff0c\u5426\u5219\u5ffd\u7565\r\n\r\n> PS\uff1a\u771f\u6b63\u7684\u9891\u7387\u9650\u5236\u8303\u56f4 = \u5173\u952e\u9650\u5236\u53c2\u6570(\u9ed8\u8ba4IP\u5730\u5740) + \u9650\u5236\u8303\u56f4(\u9ed8\u8ba4python\u7c7b\u5b8c\u6574\u8def\u5f84) + \u65b9\u6cd5\u540d(\u5982\u679c\u533a\u5206\u72ec\u7acb\u65b9\u6cd5)\uff0c\u5f53\u6b64\u9891\u7387\u8303\u56f4\u88ab\u547d\u4e2d\u540e\u624d\u4f1a\u89e6\u53d1\u9891\u7387\u9650\u5236\r\n\r\n\r\n\r\n\r\n\r\n###### \u9759\u6001\u9891\u7387\u9650\u5236(\u914d\u7f6e/\u4ee3\u7801)\r\n\r\n**controller\u7ea7\u7684\u9891\u7387\u9650\u5236**\r\n\r\n```python\r\n# coding=utf-8\r\n\r\nimport falcon\r\nfrom talos.common import decorators as deco\r\nfrom talos.common import limitwrapper\r\n\r\n# \u5feb\u901f\u81ea\u5b9a\u4e49\u4e00\u4e2a\u7b80\u5355\u652f\u6301GET\u3001POST\u8bf7\u6c42\u7684Controller\r\n# add_route('/things', ThingsController())\r\n\r\n@deco.limit('1/second')\r\nclass ThingsController(object):\r\n def on_get(self, req, resp):\r\n \"\"\"Handles GET requests, using 1/second limit\"\"\"\r\n resp.body = ('It works!')\r\n def on_post(self, req, resp):\r\n \"\"\"Handles POST requests, using global limit(if any)\"\"\"\r\n resp.body = ('It works!')\r\n```\r\n\r\n###### \u5168\u5c40\u7ea7\u7684\u9891\u7387\u9650\u5236\r\n\r\n```json\r\n{\r\n \"rate_limit\": {\r\n \"enabled\": true,\r\n \"storage_url\": \"memory://\",\r\n \"strategy\": \"fixed-window\",\r\n \"global_limits\": \"5/second\",\r\n \"per_method\": true,\r\n \"header_reset\": \"X-RateLimit-Reset\",\r\n \"header_remaining\": \"X-RateLimit-Remaining\",\r\n \"header_limit\": \"X-RateLimit-Limit\"\r\n }\r\n}\r\n```\r\n\r\n###### \u57fa\u4e8e\u4e2d\u95f4\u4ef6\u52a8\u6001\u9891\u7387\u9650\u5236\r\n\r\n\u4ee5\u4e0a\u7684\u9891\u7387\u9650\u5236\u90fd\u662f\u9884\u5b9a\u4e49\u7684\uff0c\u65e0\u6cd5\u6839\u636e\u5177\u4f53\u53c2\u6570\u8fdb\u884c\u52a8\u6001\u7684\u66f4\u6539\uff0c\u800c\u901a\u8fc7\u91cd\u5199\u4e2d\u95f4\u4ef6\u7684get_extra_limits\u51fd\u6570\uff0c\u6211\u4eec\u53ef\u4ee5\u83b7\u5f97\u52a8\u6001\u8ffd\u52a0\u9891\u7387\u9650\u5236\u7684\u80fd\u529b\r\n\r\n```python\r\nclass MyLimiter(limiter.Limiter):\r\n def __init__(self, *args, **kwargs):\r\n super(MyLimiter, self).__init__(*args, **kwargs)\r\n self.mylimits = {'cms.apps.test1': [wrapper.LimitWrapper('2/second')]}\r\n def get_extra_limits(self, request, resource, params):\r\n if request.method.lower() == 'post':\r\n return self.mylimits['cms.apps.test1']\r\n\r\n```\r\n\r\n\u9891\u7387\u9650\u5236\u9ed8\u8ba4\u88ab\u52a0\u8f7d\u5728\u4e86\u7cfb\u7edf\u7684\u4e2d\u95f4\u4ef6\u4e2d\uff0c\u5982\u679c\u4e0d\u5e0c\u671b\u91cd\u590d\u5b9a\u4e49\u4e2d\u95f4\u4ef6\uff0c\u53ef\u4ee5\u5728cms.server.wsgi_server\u4e2d\u4fee\u6539\u9879\u76ee\u6e90\u4ee3\u7801\uff1a\r\n\r\n```python\r\napplication = base.initialize_server('cms',\r\n ...\r\n middlewares=[\r\n globalvars.GlobalVars(),\r\n MyLimiter(),\r\n json_translator.JSONTranslator()],\r\n override_middlewares=True)\r\n```\r\n\r\n##### \u51fd\u6570\u7ea7\u9891\u7387\u9650\u5236\r\n\r\n```python\r\nfrom talos.common import decorators as deco\r\n\r\n@deco.limit('1/second')\r\ndef test():\r\n pass\r\n```\r\n\r\n\r\n\r\n \u7528\u4e8e\u88c5\u9970\u4e00\u4e2a\u51fd\u6570\u3001\u7c7b\u51fd\u6570\u8868\u793a\u5176\u53d7\u9650\u4e8e\u6b64\u8c03\u7528\u9891\u7387\r\n \u5f53\u88c5\u9970\u7c7b\u6210\u5458\u51fd\u6570\u65f6\uff0c\u9891\u7387\u9650\u5236\u8303\u56f4\u662f\u7c7b\u7ea7\u522b\u7684\uff0c\u610f\u5473\u7740\u7c7b\u7684\u4e0d\u540c\u5b9e\u4f8b\u5171\u4eab\u76f8\u540c\u7684\u9891\u7387\u9650\u5236\uff0c\r\n \u5982\u679c\u9700\u8981\u5b9e\u4f8b\u7ea7\u9694\u79bb\u7684\u9891\u7387\u9650\u5236\uff0c\u9700\u8981\u624b\u52a8\u6307\u5b9akey_func\uff0c\u5e76\u4f7f\u7528\u8fd4\u56de\u5b9e\u4f8b\u6807\u8bc6\u4f5c\u4e3a\u9650\u5236\u53c2\u6570\r\n\r\n :param limit_value: \u9891\u7387\u8bbe\u7f6e\uff1a\u683c\u5f0f[count] [per|/] [n (optional)][second|minute|hour|day|month|year]\r\n :param scope: \u9650\u5236\u8303\u56f4\u7a7a\u95f4\uff1a\u9ed8\u8ba4python\u7c7b/\u51fd\u6570\u5b8c\u6574\u8def\u5f84.\r\n :param key_func: \u5173\u952e\u9650\u5236\u53c2\u6570\uff1a\u9ed8\u8ba4\u4e3a\u7a7a\u5b57\u7b26\u4e32\uff0c\u81ea\u5b9a\u4e49\u51fd\u6570\uff1adef key_func(*args, **kwargs) -> string\r\n :param strategy: \u7b97\u6cd5\uff1a\u652f\u6301fixed-window\u3001fixed-window-elastic-expiry\u3001moving-window\r\n :param message: \u9519\u8bef\u63d0\u793a\u4fe1\u606f\uff1a\u9519\u8bef\u63d0\u793a\u4fe1\u606f\u53ef\u63a5\u53d73\u4e2a\u683c\u5f0f\u5316\uff08limit\uff0cremaining\uff0creset\uff09\u5185\u5bb9\r\n :param storage: \u9891\u7387\u9650\u5236\u540e\u7aef\u5b58\u50a8\u6570\u636e\uff0c\u5982: memory://, redis://:pass@localhost:6379\r\n :param hit_func: \u51fd\u6570\u5b9a\u4e49\u4e3adef hit(result) -> bool\uff0c\u4e3aTrue\u65f6\u5219\u89e6\u53d1\u9891\u7387\u9650\u5236\u5668hit\uff0c\u5426\u5219\u5ffd\u7565\r\n :param delay_hit: \u9ed8\u8ba4\u5728\u51fd\u6570\u6267\u884c\u524d\u6d4b\u8bd5\u9891\u7387hit\uff0c\u53ef\u4ee5\u8bbe\u7f6e\u4e3aTrue\u5c06\u9891\u7387\u6d4b\u8bd5hit\u653e\u7f6e\u5728\u51fd\u6570\u6267\u884c\u540e\uff0c\u642d\u914dhit_func\r\n \u4f7f\u7528\uff0c\u53ef\u4ee5\u83b7\u53d6\u5230\u51fd\u6570\u6267\u884c\u7ed3\u679c\u6765\u63a7\u5236\u662f\u5426\u6267\u884chit\r\n\u5173\u4e8e\u51fd\u6570\u9891\u7387\u9650\u5236\u6a21\u5757\u66f4\u591a\u7528\u4f8b\uff0c\u8bf7\u89c1\u5355\u5143\u6d4b\u8bd5tests.test_limit_func\r\n\r\n#### \u6570\u636e\u5e93\u7248\u672c\u7ba1\u7406\r\n\r\n\u4fee\u6539models.py\u4e3a\u6700\u7ec8\u76ee\u6807\u8868\u6a21\u578b\uff0c\u8fd0\u884c\u547d\u4ee4\uff1a\r\n\r\nalembic revision --autogenerate -m \"add table: xxxxx\"\r\n\r\n\u5907\u6ce8\u4e0d\u652f\u6301\u4e2d\u6587, autogenerate\u7528\u4e8e\u751f\u6210upgrade\uff0cdowngrade\u51fd\u6570\u5185\u5bb9\uff0c\u751f\u6210\u540e\u9700\u68c0\u67e5\u5347\u7ea7\u964d\u7ea7\u51fd\u6570\u662f\u5426\u6b63\u786e\r\n\r\n\u5347\u7ea7\uff1aalembic upgrade head\r\n\r\n\u964d\u7ea7\uff1aalembic downgrade base\r\n\r\nhead\u6307\u6700\u65b0\u7248\u672c\uff0cbase\u6307\u6700\u539f\u59cb\u7248\u672c\u5373models\u7b2c\u4e00\u4e2aversion\uff0c\u66f4\u591a\u5347\u7ea7\u964d\u7ea7\u65b9\u5f0f\u5982\u4e0b\uff1a\r\n\r\n- alembic upgrade +2 \u5347\u7ea72\u4e2a\u7248\u672c\r\n\r\n- alembic downgrade -1 \u56de\u9000\u4e00\u4e2a\u7248\u672c\r\n\r\n- alembic upgrade ae10+2 \u5347\u7ea7\u5230ae1027a6acf+2\u4e2a\u7248\u672c\r\n\r\n\r\n#### \u5355\u5143\u6d4b\u8bd5\r\n\r\ntalos\u751f\u6210\u7684\u9879\u76ee\u9884\u7f6e\u4e86\u4e00\u4e9b\u4f9d\u8d56\u8981\u6c42\uff0c\u53ef\u4ee5\u66f4\u4fbf\u6377\u7684\u4f7f\u7528pytest\u8fdb\u884c\u5355\u5143\u6d4b\u8bd5\uff0c\u5982\u9700\u4e86\u89e3\u66f4\u8be6\u7ec6\u7684\u5355\u5143\u6d4b\u8bd5\u7f16\u5199\u6307\u5bfc\uff0c\u8bf7\u67e5\u770bpytest\u6587\u6863\r\n\r\n> python setup.py test\r\n\r\n\u53ef\u4ee5\u7b80\u5355\u4ece\u547d\u4ee4\u884c\u8f93\u51fa\u4e2d\u67e5\u770b\u7ed3\u679c\uff0c\u6216\u8005\u4eceunit_test_report.html\u67e5\u770b\u5355\u5143\u6d4b\u8bd5\u62a5\u544a\uff0c\u4ecehtmlcov/index.html\u4e2d\u67e5\u770b\u8986\u76d6\u6d4b\u8bd5\u62a5\u544a\u7ed3\u679c\r\n\r\n\u793a\u4f8b\u53ef\u4ee5\u4ecetalos\u6e90\u7801\u7684tests\u6587\u4ef6\u5939\u4e2d\u67e5\u770b\r\n\r\n```bash\r\n$ tree tests\r\ntests\r\n\u251c\u2500\u2500 __init__.py\r\n\u251c\u2500\u2500 models.py\r\n\u251c\u2500\u2500 test_db_filters.py\r\n\u251c\u2500\u2500 unittest.conf\r\n\u251c\u2500\u2500 unittest.sqlite3\r\n\u2514\u2500\u2500 ...\r\n\r\n```\r\n\r\n\u5355\u5143\u6d4b\u8bd5\u6587\u4ef6\u4ee5test_xxxxxx.py\u4f5c\u4e3a\u547d\u540d\r\n\r\n## Sphinx\u6ce8\u91ca\u6587\u6863\r\n\r\nSphinx\u7684\u6ce8\u91ca\u683c\u5f0f\u8fd9\u91cc\u4e0d\u518d\u8d58\u8ff0\uff0c\u53ef\u4ee5\u53c2\u8003\u7f51\u4e0a\u6587\u6863\u6559\u7a0b\uff0ctalos\u5185\u90e8\u4f7f\u7528\u7684\u6ce8\u91ca\u6587\u6863\u683c\u5f0f\u5982\u4e0b\uff1a\r\n\r\n```\r\n \"\"\"\r\n \u51fd\u6570\u6ce8\u91ca\u6587\u6863\r\n\r\n :param value: \u53c2\u6570\u63cf\u8ff0\r\n :type value: \u53c2\u6570\u7c7b\u578b\r\n :returns: \u8fd4\u56de\u503c\u63cf\u8ff0\r\n :rtype: `bytes`/`str` \u8fd4\u56de\u503c\u7c7b\u578b\r\n \"\"\"\r\n```\r\n\r\n\r\n\r\n- \u5b89\u88c5sphinx\r\n\r\n- \u5728\u5de5\u7a0b\u76ee\u5f55\u4e0b\u8fd0\u884csphinx-quickstart\r\n\r\n - root path for the documentation [.]: docs\r\n - Project name: cms\r\n - Author name(s): Roy\r\n - Project version []: 1.0.0\r\n - Project language [en]: zh_cn\r\n - autodoc: automatically insert docstrings from modules (y/n) [n]: y\r\n\r\n- \u53ef\u9009\u7684\u98ce\u683c\u4e3b\u9898\uff0c\u63a8\u8350sphinx_rtd_theme\uff0c\u9700\u8981pip install sphinx_rtd_theme\r\n\r\n- \u4fee\u6539docs/conf.py\r\n\r\n ```python\r\n # import os\r\n # import sys\r\n # sys.path.insert(0, os.path.abspath('.'))\r\n import os\r\n import sys\r\n sys.path.insert(0, os.path.abspath('..'))\r\n\r\n import sphinx_rtd_theme\r\n html_theme = \"sphinx_rtd_theme\"\r\n html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]\r\n ```\r\n\r\n- \u751f\u6210apidoc sphinx-apidoc -o docs/ ./cms\r\n\r\n- \u751f\u6210html\uff1a\r\n\r\n - cd docs\r\n - make.bat html\r\n - \u6253\u5f00docs/_build/html/index.html\r\n\r\n## \u56fd\u9645\u5316i18n\r\n\r\n\u540c\u6837\u4ee5cms\u9879\u76ee\u4f5c\u4e3a\u4f8b\u5b50\r\n\r\n### \u63d0\u53d6\u5f85\u7ffb\u8bd1\r\n\r\n```bash\r\n# \u9700\u8981\u7ffb\u8bd1\u9879\u76ee\u7684\u8bed\u8a00\r\nfind /usr/lib/python2.7/site-packages/cms/ -name \"*.py\" >POTFILES.in\r\n# \u9700\u8981\u7ffb\u8bd1talos\u7684\u8bed\u8a00\r\nfind /usr/lib/python2.7/site-packages/talos/ -name \"*.py\" >>POTFILES.in\r\n# \u63d0\u53d6\u4e3acms.po\r\nxgettext --default-domain=cms --add-comments --keyword=_ --keyword=N_ --files-from=POTFILES.in --from-code=UTF8\r\n```\r\n\r\n\r\n\r\n### \u5408\u5e76\u5df2\u7ffb\u8bd1\r\n\r\n```bash\r\nmsgmerge cms-old.po cms.po -o cms.po\r\n```\r\n\r\n\r\n\r\n### \u7ffb\u8bd1\r\n\r\n\u53ef\u4ee5\u4f7f\u7528\u5982Poedit\u7684\u5de5\u5177\u5e2e\u52a9\u7ffb\u8bd1\r\n\r\n(\u7565)\r\n\r\n### \u7f16\u8bd1\u53d1\u5e03\r\n\r\nWindows\uff1a\u4f7f\u7528Poedit\u5de5\u5177\uff0c\u5219\u70b9\u51fb\u4fdd\u5b58\u5373\u53ef\u751f\u6210cms.mo\u6587\u4ef6\r\n\r\nLinux\uff1amsgfmt --output-file=cms.mo cms.po\r\n\r\n\u5c06mo\u6587\u4ef6\u53d1\u5e03\u5230\r\n\r\n/etc/fitportal/locale/$lang/LC_MESSAGES/\r\n\r\n$lang\u5373\u914d\u7f6e\u9879\u4e2d\u7684language\r\n\r\n\r\n\r\n## \u5de5\u5177\u5e93\r\n\r\n### \u5e26\u5bbd\u9650\u901f\r\n\r\ntalos.common.bandwidth_limiter:BandWidthLimiter\r\n\r\n### \u5bfc\u51faCSV\r\n\r\ntalos.common.exporter:export_csv\r\n\r\n### LDAP\u767b\u5f55\u8ba4\u8bc1\r\n\r\ntalos.common.ldap_util:Ldap\r\n\r\n### SMTP\u90ae\u4ef6\u53d1\u9001\r\n\r\ntalos.common.mailer:Mailer\r\n\r\n### \u8bbf\u95ee\u63a7\u5236\u89c4\u5219\u6821\u9a8c\u5668\r\n\r\ntalos.core.acl:Registry\r\n\r\n### \u5b9e\u7528\u5c0f\u51fd\u6570\r\n\r\ntalos.core.utils\r\n\r\n## \u914d\u7f6e\u9879\r\n\r\ntalos\u4e2d\u9884\u7f6e\u4e86\u5f88\u591a\u63a7\u5236\u7a0b\u5e8f\u884c\u4e3a\u7684\u914d\u7f6e\u9879\uff0c\u53ef\u4ee5\u5141\u8bb8\u7528\u6237\u8fdb\u884c\u76f8\u5173\u7684\u914d\u7f6e\uff1a\u5168\u5c40\u914d\u7f6e\u3001\u542f\u52a8\u670d\u52a1\u914d\u7f6e\u3001\u65e5\u5fd7\u914d\u7f6e\u3001\u6570\u636e\u5e93\u8fde\u63a5\u914d\u7f6e\u3001\u7f13\u5b58\u914d\u7f6e\u3001\u9891\u7387\u9650\u5236\u914d\u7f6e\u3001\u5f02\u6b65\u548c\u56de\u8c03\u914d\u7f6e\r\n\r\n| \u8def\u5f84 | \u7c7b\u578b | \u63cf\u8ff0 | \u9ed8\u8ba4\u503c |\r\n| -------------------------------------- | ------ | ------------------------------------------------------------ | ------------------------------------------------------------ |\r\n| host | string | \u4e3b\u673a\u540d | \u5f53\u524d\u4e3b\u673a\u540d |\r\n| language | string | \u7cfb\u7edf\u8bed\u8a00\u7ffb\u8bd1 | en |\r\n| locale_app | string | \u56fd\u9645\u5316locale\u5e94\u7528\u540d\u79f0 | \u5f53\u524d\u9879\u76ee\u540d |\r\n| locale_path | string | \u56fd\u9645\u5316locale\u6587\u4ef6\u8def\u5f84 | ./etc/locale |\r\n| controller.list_size_limit_enabled | bool | \u662f\u5426\u542f\u7528\u5168\u5c40\u5217\u8868\u5927\u5c0f\u9650\u5236 | False |\r\n| controller.list_size_limit | int | \u5168\u5c40\u5217\u8868\u6570\u636e\u5927\u5c0f\uff0c\u5982\u679c\u6ca1\u6709\u8bbe\u7f6e\uff0c\u5219\u9ed8\u8ba4\u8fd4\u56de\u5168\u90e8\uff0c\u5982\u679c\u7528\u6237\u4f20\u5165limit\u53c2\u6570\uff0c\u5219\u4ee5\u7528\u6237\u53c2\u6570\u4e3a\u51c6 | None |\r\n| controller.criteria_key.offset | string | controller\u63a5\u53d7\u7528\u6237\u7684offset\u53c2\u6570\u7684\u5173\u952ekey\u503c | __offset |\r\n| controller.criteria_key.limit | string | controller\u63a5\u53d7\u7528\u6237\u7684limit\u53c2\u6570\u7684\u5173\u952ekey\u503c | __limit |\r\n| controller.criteria_key.orders | string | controller\u63a5\u53d7\u7528\u6237\u7684orders\u53c2\u6570\u7684\u5173\u952ekey\u503c | __orders |\r\n| controller.criteria_key.fields | string | controller\u63a5\u53d7\u7528\u6237\u7684fields\u53c2\u6570\u7684\u5173\u952ekey\u503c | __fields |\r\n| override_defalut_middlewares | bool | \u8986\u76d6\u7cfb\u7edf\u9ed8\u8ba4\u52a0\u8f7d\u7684\u4e2d\u95f4\u4ef6 | Flase |\r\n| server | dict | \u670d\u52a1\u76d1\u542c\u914d\u7f6e\u9879 | |\r\n| server.bind | string | \u76d1\u542c\u5730\u5740 | 0.0.0.0 |\r\n| server.port | int | \u76d1\u542c\u7aef\u53e3 | 9001 |\r\n| server.backlog | int | \u76d1\u542c\u6700\u5927\u961f\u5217\u6570 | 2048 |\r\n| log | dict | \u65e5\u5fd7\u914d\u7f6e\u9879 | |\r\n| log.log_console | bool | \u662f\u5426\u5c06\u65e5\u5fd7\u91cd\u5b9a\u5411\u5230\u6807\u51c6\u8f93\u51fa | True |\r\n| log.gunicorn_access | string | gunicorn\u7684access\u65e5\u5fd7\u8def\u5f84 | ./access.log |\r\n| log.gunicorn_error | string | gunicorn\u7684error\u65e5\u5fd7\u8def\u5f84 | ./error.log |\r\n| log.path | string | \u5168\u5c40\u65e5\u5fd7\u8def\u5f84 | ./server.log |\r\n| log.level | string | \u65e5\u5fd7\u7ea7\u522b | INFO |\r\n| log.format_string | string | \u65e5\u5fd7\u5b57\u6bb5\u914d\u7f6e | %(asctime)s.%(msecs)03d %(process)d %(levelname)s %(name)s:%(lineno)d [-] %(message)s |\r\n| log.date_format_string | string | \u65e5\u5fd7\u65f6\u95f4\u683c\u5f0f | %Y-%m-%d %H:%M:%S |\r\n| log.loggers | list | \u6a21\u5757\u72ec\u7acb\u65e5\u5fd7\u914d\u7f6e\uff0c\u5217\u8868\u6bcf\u4e2a\u5143\u7d20\u662fdict: [{\"name\": \"cms.test.api\", \"path\": \"api.log\"}] | |\r\n| log.loggers.name | string | \u6a21\u5757\u540d\u79f0\u8def\u5f84\uff0c\u5982cms.apps.test | |\r\n| log.loggers.level | string | \u65e5\u5fd7\u7ea7\u522b | |\r\n| log.loggers.path | string | \u65e5\u5fd7\u8def\u5f84 | |\r\n| db | dict | \u9ed8\u8ba4\u6570\u636e\u5e93\u914d\u7f6e\u9879\uff0c\u7528\u6237\u53ef\u4ee5\u81ea\u884c\u5b9a\u4e49\u5176\u4ed6DB\u914d\u7f6e\u9879\uff0c\u4f46\u9700\u8981\u81ea\u5df1\u521d\u59cb\u5316DBPool\u5bf9\u8c61(\u53ef\u4ee5\u53c2\u8003DefaultDBPool\u8fdb\u884c\u5355\u4f8b\u63a7\u5236) | |\r\n| db.connection | string | \u8fde\u63a5\u5b57\u7b26\u4e32 | |\r\n| db.pool_size | int | \u8fde\u63a5\u6c60\u5927\u5c0f | 3 |\r\n| db.pool_recycle | int | \u8fde\u63a5\u6700\u5927\u7a7a\u95f2\u65f6\u95f4\uff0c\u8d85\u8fc7\u65f6\u95f4\u540e\u81ea\u52a8\u56de\u6536 | 3600 |\r\n| db.pool_timeout | int | \u83b7\u53d6\u8fde\u63a5\u8d85\u65f6\u65f6\u95f4\uff0c\u5355\u4f4d\u79d2 | 5 |\r\n| db.max_overflow | int | \u7a81\u53d1\u8fde\u63a5\u6c60\u6269\u5c55\u5927\u5c0f | 5 |\r\n| dbcrud | dict | \u6570\u636e\u5e93CRUD\u63a7\u5236\u9879 | |\r\n| dbcrud.unsupported_filter_as_empty | bool | \u5f53\u9047\u5230\u4e0d\u652f\u6301\u7684filter\u65f6\u7684\u9ed8\u8ba4\u884c\u4e3a\uff0c1\u662f\u8fd4\u56de\u7a7a\u7ed3\u679c\uff0c2\u662f\u5ffd\u7565\u4e0d\u652f\u6301\u7684\u6761\u4ef6\uff0c\u7531\u4e8e\u5386\u53f2\u7248\u672c\u7684\u884c\u4e3a\u9ed8\u8ba4\u4e3a2\uff0c\u56e0\u6b64\u5176\u9ed8\u8ba4\u503c\u4e3aFalse\uff0c\u5373\u5ffd\u7565\u4e0d\u652f\u6301\u7684\u6761\u4ef6 | False |\r\n| cache | dict | \u7f13\u5b58\u914d\u7f6e\u9879 | |\r\n| cache.type | string | \u7f13\u5b58\u540e\u7aef\u7c7b\u578b | dogpile.cache.memory |\r\n| cache.expiration_time | int | \u7f13\u5b58\u9ed8\u8ba4\u8d85\u65f6\u65f6\u95f4\uff0c\u5355\u4f4d\u4e3a\u79d2 | 3600 |\r\n| cache.arguments | dict | \u7f13\u5b58\u989d\u5916\u914d\u7f6e | None |\r\n| application | dict | | |\r\n| application.names | list | \u52a0\u8f7d\u7684\u5e94\u7528\u5217\u8868\uff0c\u6bcf\u4e2a\u5143\u7d20\u4e3astring\uff0c\u4ee3\u8868\u52a0\u8f7d\u7684app\u8def\u5f84 | [] |\r\n| rate_limit | dict | \u9891\u7387\u9650\u5236\u914d\u7f6e\u9879 | |\r\n| rate_limit.enabled | bool | \u662f\u5426\u542f\u7528\u9891\u7387\u9650\u5236 | False |\r\n| rate_limit.storage_url | string | \u9891\u7387\u9650\u5236\u6570\u636e\u5b58\u50a8\u8ba1\u7b97\u540e\u7aef | memory:// |\r\n| rate_limit.strategy | string | \u9891\u7387\u9650\u5236\u7b97\u6cd5\uff0c\u53ef\u9009fixed-window\uff0cfixed-window-elastic-expiry\uff0cmoving-window | fixed-window |\r\n| rate_limit.global_limits | string | \u5168\u5c40\u9891\u7387\u9650\u5236(\u4f9d\u8d56\u4e8e\u5168\u5c40\u4e2d\u95f4\u4ef6)\uff0ceg. 1/second; 5/minute | None |\r\n| rate_limit.per_method | bool | \u662f\u5426\u4e3a\u6bcf\u4e2aHTTP\u65b9\u6cd5\u72ec\u7acb\u9891\u7387\u9650\u5236 | True |\r\n| rate_limit.header_reset | string | HTTP\u54cd\u5e94\u5934\uff0c\u9891\u7387\u91cd\u7f6e\u65f6\u95f4 | X-RateLimit-Reset |\r\n| rate_limit.header_remaining | string | HTTP\u54cd\u5e94\u5934\uff0c\u5269\u4f59\u7684\u8bbf\u95ee\u6b21\u6570 | X-RateLimit-Remaining |\r\n| rate_limit.header_limit | string | HTTP\u54cd\u5e94\u5934\uff0c\u6700\u5927\u8bbf\u95ee\u6b21\u6570 | X-RateLimit-Limit |\r\n| celery | dict | \u5f02\u6b65\u4efb\u52a1\u914d\u7f6e\u9879 | |\r\n| celery.talos_on_user_schedules_changed | list | \u5b9a\u65f6\u4efb\u52a1\u53d8\u66f4\u5224\u65ad\u51fd\u6570\u5217\u8868\"talos_on_user_schedules_changed\":[\"cms.workers.hooks:ChangeDetection\"], | |\r\n| celery.talos_on_user_schedules | list | \u5b9a\u65f6\u4efb\u52a1\u51fd\u6570\u5217\u8868\"talos_on_user_schedules\": [\"cms.workers.hooks:AllSchedules\"] | |\r\n| worker | dict | \u5f02\u6b65\u5de5\u4f5c\u8fdb\u7a0b\u914d\u7f6e\u9879 | |\r\n| worker.callback | dict | \u5f02\u6b65\u5de5\u4f5c\u8fdb\u7a0b\u56de\u8c03\u63a7\u5236\u914d\u7f6e\u9879 | |\r\n| worker.callback.strict_client | bool | \u5f02\u6b65\u5de5\u4f5c\u8fdb\u7a0b\u8ba4\u8bc1\u65f6\u4ec5\u4f7f\u7528\u76f4\u8fdeIP | True |\r\n| worker.callback.allow_hosts | list | \u5f02\u6b65\u5de5\u4f5c\u8fdb\u7a0b\u8ba4\u8bc1\u4e3b\u673aIP\u5217\u8868\uff0c\u5f53\u8bbe\u7f6e\u65f6\uff0c\u4ec5\u5141\u8bb8\u5217\u8868\u5185worker\u8c03\u7528\u56de\u8c03 | None |\r\n| worker.callback.name.%s.allow_hosts | list | \u5f02\u6b65\u5de5\u4f5c\u8fdb\u7a0b\u8ba4\u8bc1\u65f6\uff0c\u4ec5\u5141\u8bb8\u5217\u8868\u5185worker\u8c03\u7528\u6b64\u547d\u540d\u56de\u8c03 | None |\r\n\r\n\r\n\r\n[^1]: \u672c\u6587\u6863\u57fa\u4e8ev1.1.8\u7248\u672c\uff0c\u5e76\u589e\u52a0\u4e86\u540e\u7eed\u7248\u672c\u7684\u4e00\u4e9b\u7279\u6027\u63cf\u8ff0\r\n[^ 2]: v1.1.9\u7248\u672c\u4e2d\u65b0\u589e\u4e86TScheduler\u652f\u6301\u52a8\u6001\u7684\u5b9a\u65f6\u4efb\u52a1\u4ee5\u53ca\u66f4\u4e30\u5bcc\u7684\u914d\u7f6e\u5b9a\u4e49\u5b9a\u65f6\u4efb\u52a1\r\n[^ 3]: v1.1.8\u7248\u672c\u4e2d\u4ec5\u652f\u6301\u8fd9\u7c7b\u7b80\u5355\u7684\u5b9a\u65f6\u4efb\u52a1\r\n[^ 4]: v1.2.0\u7248\u672c\u589e\u52a0\u4e86__fields\u5b57\u6bb5\u9009\u62e9 \u4ee5\u53ca null, notnull, nlike, nilike\u7684\u67e5\u8be2\u6761\u4ef6 \u4ee5\u53ca relationship\u67e5\u8be2\u652f\u6301\r\n[^ 5]: v1.2.0\u7248\u672c\u65b0\u589e$or,$and\u67e5\u8be2\u652f\u6301\r\n\r\n", "description_content_type": "", "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://gitee.com/wu.jianjun/talos", "keywords": "talos automation restful rest api celery sqlalchemy falcon", "license": "Apache License 2.0", "maintainer": "", "maintainer_email": "", "name": "talos-api", "package_url": "https://pypi.org/project/talos-api/", "platform": "", "project_url": "https://pypi.org/project/talos-api/", "project_urls": { "Homepage": "https://gitee.com/wu.jianjun/talos" }, "release_url": "https://pypi.org/project/talos-api/1.2.2/", "requires_dist": [ "falcon", "six (>=1.9.0)", "SQLAlchemy (>=1.1.0)", "ipaddress", "mako", "limits", "pytest (<4.1) ; extra == 'testing'", "pytest-runner ; extra == 'testing'", "pytest-html ; extra == 'testing'", "pytest-cov ; extra == 'testing'" ], "requires_python": "", "summary": "A Falcon Base, Powerful RESTful API Framework", "version": "1.2.2" }, "last_serial": 5129756, "releases": { "1.2.0": [ { "comment_text": "", "digests": { "md5": "e1ae6c363e27673dd026d36389389de8", "sha256": "b05442e27e78484981e65de469986dc5bc76f50fba754c20c9c5ba6d44f6eda0" }, "downloads": -1, "filename": "talos_api-1.2.0-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "e1ae6c363e27673dd026d36389389de8", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 111598, "upload_time": "2019-01-24T06:21:39", "url": "https://files.pythonhosted.org/packages/9e/00/11110a4ec178a85b962eaa20bd553560e13c001c15267879968491df7ba9/talos_api-1.2.0-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "d71b47fcf102d5d2a3187fa5370aa68b", "sha256": "9f16e07da5ad9ebe4931e7d514f584760741b0f5909894df4a9d55044819b5e0" }, "downloads": -1, "filename": "talos-api-1.2.0.tar.gz", "has_sig": false, "md5_digest": "d71b47fcf102d5d2a3187fa5370aa68b", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 111389, "upload_time": "2019-01-24T06:21:41", "url": "https://files.pythonhosted.org/packages/69/d9/344c6a4f68d6a1afbe8066eb57b35478691e191633dd1e555b77bce8e125/talos-api-1.2.0.tar.gz" } ], "1.2.1": [ { "comment_text": "", "digests": { "md5": "3e38ed1eeb5f867b8004704178350b27", "sha256": "d4808add646b544f3535057400eaefe079bee77d4cf3bc5f8faeceacb8a3139d" }, "downloads": -1, "filename": "talos_api-1.2.1-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "3e38ed1eeb5f867b8004704178350b27", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 112505, "upload_time": "2019-02-12T06:05:11", "url": "https://files.pythonhosted.org/packages/f5/69/d079e7d49aa3072ac316888876341c5a83bb75f0cd9509240e5efe20c9f3/talos_api-1.2.1-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "87249b3db5af8d43153a2ab02fe33587", "sha256": "046d67f71bf856b4458163dbfb5d4754533841a3f548e66a04f92247331effb4" }, "downloads": -1, "filename": "talos-api-1.2.1.tar.gz", "has_sig": false, "md5_digest": "87249b3db5af8d43153a2ab02fe33587", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 112832, "upload_time": "2019-02-12T06:05:17", "url": "https://files.pythonhosted.org/packages/24/68/9c535071fba47d99ffae4c8aa8814a2804512be8313ed2c41e0c93084f44/talos-api-1.2.1.tar.gz" } ], "1.2.2": [ { "comment_text": "", "digests": { "md5": "09e97d1414c15c516f84f7786d3d8fdf", "sha256": "17d033c70c97d675efa9fd67ba8ced8f7ba4b5f2fe231b271e3e2e72d61feeca" }, "downloads": -1, "filename": "talos_api-1.2.2-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "09e97d1414c15c516f84f7786d3d8fdf", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 103193, "upload_time": "2019-04-11T16:10:58", "url": "https://files.pythonhosted.org/packages/53/f4/e66a36966fac8ebe237b1c3cfbb2de883952ee4468384b9b63fa9db24a5a/talos_api-1.2.2-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "5f876ba7f501c823eaefa8bea0571190", "sha256": "8aaf2ce0058ef62c208bb441e5b32fdeae53f3fefd5ef2be6ce1e3e30eb80efc" }, "downloads": -1, "filename": "talos-api-1.2.2.tar.gz", "has_sig": false, "md5_digest": "5f876ba7f501c823eaefa8bea0571190", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 120331, "upload_time": "2019-04-11T16:11:00", "url": "https://files.pythonhosted.org/packages/7e/00/366c009692156ae7b09f129da91d7c4eca4de947de89ce091f1bec1f6774/talos-api-1.2.2.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "09e97d1414c15c516f84f7786d3d8fdf", "sha256": "17d033c70c97d675efa9fd67ba8ced8f7ba4b5f2fe231b271e3e2e72d61feeca" }, "downloads": -1, "filename": "talos_api-1.2.2-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "09e97d1414c15c516f84f7786d3d8fdf", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 103193, "upload_time": "2019-04-11T16:10:58", "url": "https://files.pythonhosted.org/packages/53/f4/e66a36966fac8ebe237b1c3cfbb2de883952ee4468384b9b63fa9db24a5a/talos_api-1.2.2-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "5f876ba7f501c823eaefa8bea0571190", "sha256": "8aaf2ce0058ef62c208bb441e5b32fdeae53f3fefd5ef2be6ce1e3e30eb80efc" }, "downloads": -1, "filename": "talos-api-1.2.2.tar.gz", "has_sig": false, "md5_digest": "5f876ba7f501c823eaefa8bea0571190", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 120331, "upload_time": "2019-04-11T16:11:00", "url": "https://files.pythonhosted.org/packages/7e/00/366c009692156ae7b09f129da91d7c4eca4de947de89ce091f1bec1f6774/talos-api-1.2.2.tar.gz" } ] }