[{"data":1,"prerenderedAt":718},["ShallowReactive",2],{"navigation":3,"post-\u002Fposts\u002F2023\u002Fgitlab-ci-auto-deploy-python-lib":20,"surroundPosts-\u002Fposts\u002F2023\u002Fgitlab-ci-auto-deploy-python-lib":706},[4,8,12,16],{"title":5,"path":6,"stem":7},"首页","\u002F","00.index",{"title":9,"path":10,"stem":11},"文章","\u002Fposts","01.posts",{"title":13,"path":14,"stem":15},"动态","\u002Fmoments","02.moments",{"title":17,"path":18,"stem":19},"关于","\u002Fabout","09.about",{"id":21,"title":22,"body":23,"class":686,"cover":687,"coverSize":686,"date":688,"description":29,"draft":689,"extension":690,"hideComments":689,"location":686,"meta":691,"navigation":69,"path":692,"readingTime":693,"seo":698,"sitemap":699,"stem":700,"tags":701,"time":686,"weather":686,"__hash__":705},"posts\u002Fposts\u002F2023\u002F20230423.gitlab-ci-auto-deploy-python-lib.md","GitLab CI 配置自动化打包上传 Python 库",{"type":24,"value":25,"toc":684},"minimark",[26,30,42,436,442,570,589,606,609,680],[27,28,29],"p",{},"自己之前有些 python 脚本类的项目，会用到一些通用的能力，如读取配置、打日志等，每次都 copy 一份 utils 目录有些不够优雅，于是撸了一个公共库，方便自己使用。",[27,31,32,33,37,38,41],{},"为了能配合 GitLab CI，",[34,35,36],"code",{},"setup.py"," 需要做一些小调整，版本号不需要手动输入了，直接读取 ",[34,39,40],{},"$CI_COMMIT_TAG","，代码如下：",[43,44,49],"pre",{"className":45,"code":46,"language":47,"meta":48,"style":48},"language-python shiki shiki-themes material-theme-lighter github-light github-dark","import os\n\nimport setuptools\n\nwith open(\"README.md\", \"r\") as fh:\n    long_description = fh.read()\n\nsetuptools.setup(\n    name=\"yuanfen\",\n    version=os.environ.get(\"CI_COMMIT_TAG\", \"0.0.0\"),\n    author=\"Bean\",\n    author_email=\"bean@yuanfen.net\",\n    description=\"Yuanfen Python Library\",\n    long_description=long_description,\n    long_description_content_type=\"text\u002Fmarkdown\",\n    url=\"\",\n    install_requires=[\"pyyaml\", \"watchdog\"],\n    packages=setuptools.find_packages(),\n    classifiers=[\n        \"Programming Language :: Python :: 3\",\n        \"License :: OSI Approved :: MIT License\",\n        \"Operating System :: OS Independent\",\n    ],\n)\n\n","python","",[34,50,51,64,71,79,84,131,153,158,172,191,234,251,268,285,298,315,328,358,376,387,400,412,424,430],{"__ignoreMap":48},[52,53,56,60],"span",{"class":54,"line":55},"line",1,[52,57,59],{"class":58},"sVHd0","import",[52,61,63],{"class":62},"su5hD"," os\n",[52,65,67],{"class":54,"line":66},2,[52,68,70],{"emptyLinePlaceholder":69},true,"\n",[52,72,74,76],{"class":54,"line":73},3,[52,75,59],{"class":58},[52,77,78],{"class":62}," setuptools\n",[52,80,82],{"class":54,"line":81},4,[52,83,70],{"emptyLinePlaceholder":69},[52,85,87,90,94,98,102,106,108,111,114,117,119,122,125,128],{"class":54,"line":86},5,[52,88,89],{"class":58},"with",[52,91,93],{"class":92},"sptTA"," open",[52,95,97],{"class":96},"sP7_E","(",[52,99,101],{"class":100},"sjJ54","\"",[52,103,105],{"class":104},"s_sjI","README.md",[52,107,101],{"class":100},[52,109,110],{"class":96},",",[52,112,113],{"class":100}," \"",[52,115,116],{"class":104},"r",[52,118,101],{"class":100},[52,120,121],{"class":96},")",[52,123,124],{"class":58}," as",[52,126,127],{"class":62}," fh",[52,129,130],{"class":96},":\n",[52,132,134,137,141,143,146,150],{"class":54,"line":133},6,[52,135,136],{"class":62},"    long_description ",[52,138,140],{"class":139},"smGrS","=",[52,142,127],{"class":62},[52,144,145],{"class":96},".",[52,147,149],{"class":148},"slqww","read",[52,151,152],{"class":96},"()\n",[52,154,156],{"class":54,"line":155},7,[52,157,70],{"emptyLinePlaceholder":69},[52,159,161,164,166,169],{"class":54,"line":160},8,[52,162,163],{"class":62},"setuptools",[52,165,145],{"class":96},[52,167,168],{"class":148},"setup",[52,170,171],{"class":96},"(\n",[52,173,175,179,181,183,186,188],{"class":54,"line":174},9,[52,176,178],{"class":177},"s99_P","    name",[52,180,140],{"class":139},[52,182,101],{"class":100},[52,184,185],{"class":104},"yuanfen",[52,187,101],{"class":100},[52,189,190],{"class":96},",\n",[52,192,194,197,199,202,204,208,210,213,215,217,220,222,224,226,229,231],{"class":54,"line":193},10,[52,195,196],{"class":177},"    version",[52,198,140],{"class":139},[52,200,201],{"class":148},"os",[52,203,145],{"class":96},[52,205,207],{"class":206},"skxfh","environ",[52,209,145],{"class":96},[52,211,212],{"class":148},"get",[52,214,97],{"class":96},[52,216,101],{"class":100},[52,218,219],{"class":104},"CI_COMMIT_TAG",[52,221,101],{"class":100},[52,223,110],{"class":96},[52,225,113],{"class":100},[52,227,228],{"class":104},"0.0.0",[52,230,101],{"class":100},[52,232,233],{"class":96},"),\n",[52,235,237,240,242,244,247,249],{"class":54,"line":236},11,[52,238,239],{"class":177},"    author",[52,241,140],{"class":139},[52,243,101],{"class":100},[52,245,246],{"class":104},"Bean",[52,248,101],{"class":100},[52,250,190],{"class":96},[52,252,254,257,259,261,264,266],{"class":54,"line":253},12,[52,255,256],{"class":177},"    author_email",[52,258,140],{"class":139},[52,260,101],{"class":100},[52,262,263],{"class":104},"bean@yuanfen.net",[52,265,101],{"class":100},[52,267,190],{"class":96},[52,269,271,274,276,278,281,283],{"class":54,"line":270},13,[52,272,273],{"class":177},"    description",[52,275,140],{"class":139},[52,277,101],{"class":100},[52,279,280],{"class":104},"Yuanfen Python Library",[52,282,101],{"class":100},[52,284,190],{"class":96},[52,286,288,291,293,296],{"class":54,"line":287},14,[52,289,290],{"class":177},"    long_description",[52,292,140],{"class":139},[52,294,295],{"class":148},"long_description",[52,297,190],{"class":96},[52,299,301,304,306,308,311,313],{"class":54,"line":300},15,[52,302,303],{"class":177},"    long_description_content_type",[52,305,140],{"class":139},[52,307,101],{"class":100},[52,309,310],{"class":104},"text\u002Fmarkdown",[52,312,101],{"class":100},[52,314,190],{"class":96},[52,316,318,321,323,326],{"class":54,"line":317},16,[52,319,320],{"class":177},"    url",[52,322,140],{"class":139},[52,324,325],{"class":100},"\"\"",[52,327,190],{"class":96},[52,329,331,334,336,339,341,344,346,348,350,353,355],{"class":54,"line":330},17,[52,332,333],{"class":177},"    install_requires",[52,335,140],{"class":139},[52,337,338],{"class":96},"[",[52,340,101],{"class":100},[52,342,343],{"class":104},"pyyaml",[52,345,101],{"class":100},[52,347,110],{"class":96},[52,349,113],{"class":100},[52,351,352],{"class":104},"watchdog",[52,354,101],{"class":100},[52,356,357],{"class":96},"],\n",[52,359,361,364,366,368,370,373],{"class":54,"line":360},18,[52,362,363],{"class":177},"    packages",[52,365,140],{"class":139},[52,367,163],{"class":148},[52,369,145],{"class":96},[52,371,372],{"class":148},"find_packages",[52,374,375],{"class":96},"(),\n",[52,377,379,382,384],{"class":54,"line":378},19,[52,380,381],{"class":177},"    classifiers",[52,383,140],{"class":139},[52,385,386],{"class":96},"[\n",[52,388,390,393,396,398],{"class":54,"line":389},20,[52,391,392],{"class":100},"        \"",[52,394,395],{"class":104},"Programming Language :: Python :: 3",[52,397,101],{"class":100},[52,399,190],{"class":96},[52,401,403,405,408,410],{"class":54,"line":402},21,[52,404,392],{"class":100},[52,406,407],{"class":104},"License :: OSI Approved :: MIT License",[52,409,101],{"class":100},[52,411,190],{"class":96},[52,413,415,417,420,422],{"class":54,"line":414},22,[52,416,392],{"class":100},[52,418,419],{"class":104},"Operating System :: OS Independent",[52,421,101],{"class":100},[52,423,190],{"class":96},[52,425,427],{"class":54,"line":426},23,[52,428,429],{"class":96},"    ],\n",[52,431,433],{"class":54,"line":432},24,[52,434,435],{"class":96},")\n",[27,437,438,441],{},[34,439,440],{},".gitlab-ci.yml"," 代码如下：",[43,443,447],{"className":444,"code":445,"language":446,"meta":48,"style":48},"language-yaml shiki shiki-themes material-theme-lighter github-light github-dark","image: python:3\n\nstages:\n  - deploy\n\ndeploy:\n  stage: deploy\n  variables:\n    TWINE_USERNAME: $TWINE_USERNAME\n    TWINE_PASSWORD: $TWINE_PASSWORD\n  script:\n    - python setup.py sdist bdist_wheel\n    - pip install twine -i https:\u002F\u002Fpypi.tuna.tsinghua.edu.cn\u002Fsimple\n    - twine upload dist\u002F*\n  only:\n    - tags\n","yaml",[34,448,449,461,465,472,480,484,491,500,507,517,527,534,542,549,556,563],{"__ignoreMap":48},[52,450,451,455,458],{"class":54,"line":55},[52,452,454],{"class":453},"sQzsp","image",[52,456,457],{"class":96},":",[52,459,460],{"class":104}," python:3\n",[52,462,463],{"class":54,"line":66},[52,464,70],{"emptyLinePlaceholder":69},[52,466,467,470],{"class":54,"line":73},[52,468,469],{"class":453},"stages",[52,471,130],{"class":96},[52,473,474,477],{"class":54,"line":81},[52,475,476],{"class":96},"  -",[52,478,479],{"class":104}," deploy\n",[52,481,482],{"class":54,"line":86},[52,483,70],{"emptyLinePlaceholder":69},[52,485,486,489],{"class":54,"line":133},[52,487,488],{"class":453},"deploy",[52,490,130],{"class":96},[52,492,493,496,498],{"class":54,"line":155},[52,494,495],{"class":453},"  stage",[52,497,457],{"class":96},[52,499,479],{"class":104},[52,501,502,505],{"class":54,"line":160},[52,503,504],{"class":453},"  variables",[52,506,130],{"class":96},[52,508,509,512,514],{"class":54,"line":174},[52,510,511],{"class":453},"    TWINE_USERNAME",[52,513,457],{"class":96},[52,515,516],{"class":104}," $TWINE_USERNAME\n",[52,518,519,522,524],{"class":54,"line":193},[52,520,521],{"class":453},"    TWINE_PASSWORD",[52,523,457],{"class":96},[52,525,526],{"class":104}," $TWINE_PASSWORD\n",[52,528,529,532],{"class":54,"line":236},[52,530,531],{"class":453},"  script",[52,533,130],{"class":96},[52,535,536,539],{"class":54,"line":253},[52,537,538],{"class":96},"    -",[52,540,541],{"class":104}," python setup.py sdist bdist_wheel\n",[52,543,544,546],{"class":54,"line":270},[52,545,538],{"class":96},[52,547,548],{"class":104}," pip install twine -i https:\u002F\u002Fpypi.tuna.tsinghua.edu.cn\u002Fsimple\n",[52,550,551,553],{"class":54,"line":287},[52,552,538],{"class":96},[52,554,555],{"class":104}," twine upload dist\u002F*\n",[52,557,558,561],{"class":54,"line":300},[52,559,560],{"class":453},"  only",[52,562,130],{"class":96},[52,564,565,567],{"class":54,"line":317},[52,566,538],{"class":96},[52,568,569],{"class":104}," tags\n",[27,571,572,573,576,577,580,581,584,585,588],{},"其中，需要在 GitLab 项目 ",[34,574,575],{},"Setting -> CI\u002FCD -> Variables"," 中配置 ",[34,578,579],{},"TWINE_USERNAME"," 和 ",[34,582,583],{},"TWINE_PASSWORD","，这个是你在 ",[34,586,587],{},"pypi.org"," 上的用户名和密码。",[27,590,591,592,594,595,598,599,602,603,605],{},"另外需要注意的是，仓库的目录结构，",[34,593,36],{}," 中指定的 ",[34,596,597],{},"name"," 是包名，而 ",[34,600,601],{},"__init__.py"," 所在的文件夹名是实际 ",[34,604,59],{}," 的导入名。上传包时，PyPI 会检查报名是否重复，但不会检查导入名，所以如果定义了一个容易导致重复的导入名的话，就有可能有冲突。所以一般情况下该文件夹尽量是与包名统一，这样不容易有冲突。",[27,607,608],{},"以下是 ChatGPT 给出的目录结构的建议：",[610,611,612,615,623,642,648,654,663,668,674],"blockquote",{},[27,613,614],{},"当你创建一个 Python 库时，通常需要遵循一些标准的目录结构和文件命名规范。下面是一个常见的建议目录结构，包括了一些常见文件和目录：",[43,616,621],{"className":617,"code":619,"language":620},[618],"language-text","your_library\u002F\n   your_library\u002F\n       __init__.py\n       module1.py\n       module2.py\n       ...\n   examples\u002F\n       example1.py\n       example2.py\n       ...\n   tests\u002F\n       test_module1.py\n       test_module2.py\n       ...\n   README.md\n   LICENSE\n   setup.py\n   requirements.txt\n   .gitignore\n\n","text",[34,622,619],{"__ignoreMap":48},[27,624,625,626,629,630,632,633,635,636,638,639,641],{},"在这个目录结构中，",[34,627,628],{},"your_library"," 是你的库的名称，它应该与 ",[34,631,36],{}," 文件中定义的名称相同。",[34,634,628],{}," 目录包含了你的库的所有源代码和模块文件，以及 ",[34,637,601],{}," 文件，用于将 ",[34,640,628],{}," 目录转换为 Python 包。",[27,643,644,647],{},[34,645,646],{},"examples"," 目录包含了一些示例代码，用于演示如何使用你的库。这些示例代码可以帮助用户更快地了解和使用你的库。",[27,649,650,653],{},[34,651,652],{},"tests"," 目录包含了单元测试和集成测试代码，用于确保你的库的功能和质量。在这个目录中，你可以为每个模块编写一个或多个测试文件，并使用测试框架运行这些测试。",[27,655,656,658,659,662],{},[34,657,105],{}," 文件是你的库的说明文档，用于介绍你的库的功能、用法和示例。",[34,660,661],{},"LICENSE"," 文件包含了你的库的许可证信息，用于明确你的库的使用和分发条件。",[27,664,665,667],{},[34,666,36],{}," 文件是你的库的安装脚本，用于将你的库安装到 Python 环境中。在这个文件中，你需要指定库的名称、版本、作者、描述、依赖等信息。",[27,669,670,673],{},[34,671,672],{},"requirements.txt"," 文件包含了你的库依赖的其他 Python 包的名称和版本号。在使用 pip 安装你的库时，pip 将自动安装这些依赖包。",[27,675,676,679],{},[34,677,678],{},".gitignore"," 文件是 Git 版本控制系统的忽略文件，用于指定哪些文件或目录不需要被 Git 跟踪和提交。",[681,682,683],"style",{},"html pre.shiki code .sVHd0, html code.shiki .sVHd0{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#D73A49;--shiki-default-font-style:inherit;--shiki-dark:#F97583;--shiki-dark-font-style:inherit}html pre.shiki code .su5hD, html code.shiki .su5hD{--shiki-light:#90A4AE;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sptTA, html code.shiki .sptTA{--shiki-light:#6182B8;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sP7_E, html code.shiki .sP7_E{--shiki-light:#39ADB5;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sjJ54, html code.shiki .sjJ54{--shiki-light:#39ADB5;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .s_sjI, html code.shiki .s_sjI{--shiki-light:#91B859;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .smGrS, html code.shiki .smGrS{--shiki-light:#39ADB5;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .slqww, html code.shiki .slqww{--shiki-light:#6182B8;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s99_P, html code.shiki .s99_P{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#E36209;--shiki-default-font-style:inherit;--shiki-dark:#FFAB70;--shiki-dark-font-style:inherit}html pre.shiki code .skxfh, html code.shiki .skxfh{--shiki-light:#E53935;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sQzsp, html code.shiki .sQzsp{--shiki-light:#E53935;--shiki-default:#22863A;--shiki-dark:#85E89D}",{"title":48,"searchDepth":66,"depth":66,"links":685},[],null,"jpg","2023-04-23",false,"md",{},"\u002Fposts\u002F2023\u002Fgitlab-ci-auto-deploy-python-lib",{"text":694,"minutes":695,"time":696,"words":697},"4 min read",3.835,230100,767,{"title":22,"description":29},{"loc":692},"posts\u002F2023\u002F20230423.gitlab-ci-auto-deploy-python-lib",[702,703,704],"技术","DevOps","Python","o6Yb6IDvjBzm2kTY_GeQKG92soCUwh_mh3f6D78Xm-s",[707,713],{"title":708,"path":709,"stem":710,"date":711,"description":712,"children":-1},"上海居转户落户完整经历","\u002Fposts\u002F2023\u002Fshanghai-luohu","posts\u002F2023\u002F20230508.shanghai-luohu","2023-05-08","万里长征，终于到了最后一步，是时候把历时 3 年多的上海落户的经历记录下来了，帮助后人。",{"title":714,"path":715,"stem":716,"date":688,"description":717,"children":-1},"TimedRotatingFileHandler 不会自动清除旧日志的问题","\u002Fposts\u002F2023\u002Ftimedrotatingfilehandler-backupcount-problem","posts\u002F2023\u002F20230423.timedrotatingfilehandler-backupcount-problem","话不多说，直接贴代码：",1777579138664]