分类目录归档:技术

Thinking in Component Tree

在开发前端应用的时候,我比较推荐在真正开始写代码之前试着画一画组件树 / 状态树。

在很多时候,可能你的设计师已经帮你做好了组件树,但在某些场景下,你的设计时并不会帮你拆解组件树,或者是你是直接和产品经理对接,他不会帮你拆解组件树。

这个时候,相比于写代码,我更推荐你先拆解组件树,在完成组件树之后,再开始你的 Coding。

d2b5ca33bd970f64a6301fa75ae2eb22 5

Figma / Sketch 之类的软件提供的分组能力、图层的能力,可以帮助你将组件合理的拆解、分组、归类。当你完成树的建设之后,可以试试看将不同的模块拆解,每个模块是否可以独立正常的运转。如果不可以,则说明你的状态拆解的可能是有问题的。

当你完成拆解之后,只需要按照你拆解出来的树组织你的 Component 即可。

在 WordPress 的 Docker 镜像上加装 Redis 拓展,以支持 Redis 缓存

从 LAMP 到 Docker based PaaS 工具 当中,我提到我现在使用的是 Docker Based PaaS 产品来托管站点。本站目前其实就是跑在 Docker 上的。

使用默认的 WordPress 镜像时,我发现一个问题:没有支持 Redis 拓展!我使用 Redis 来缓存 Query,提升访问的性能。如果缺失了 Redis 拓展,就会减少一部分缓存的能力。于是开始研究如何在官方的 WordPress 镜像上加入 Redis 拓展。

根据 WordPress 镜像的官方说明,我们可以 docker-php-ext-* 命令来配置镜像,安装必要的拓展,来满足我们日常使用的需求,并给出了官方的参考。

不过,我在验证 Redis 拓展时,使用 docker-php-ext-* 命令没有配置成功,好在可以使用 pecl 来安装。于是,我便将 Dockerfile 修改成如下内容,来完成对于 Redis 拓展的安装。

FROM wordpress:latest
RUN pecl install -o -f redis && rm -rf /tmp/pear && docker-php-ext-enable redis

修改好 Dockerfile ,然后重新启动,一切都好了~

d2b5ca33bd970f64a6301fa75ae2eb22 4

使用 idb-kayval 作为前端数据存储

在前端留存一些状态,是在前端场景下提升性能的常规操作。最近我有一个场景需要在前端留存一个状态,借着这个机会,试了试 IndexedDB 来作为数据存储,拓展一下新的方向。

关于 Indexed DB

Chrome 在中提供了多种不同的存储,按下 F12 ,打开 DevTools ,找到应用 – 存储,你就会看到目前 Chrome 支持的多种存储方式。常用的主要就是本次存储空间(Local Storage)、会话存储空间(Session Storage)和 Indexed DB。这次我用的便是 Indexed DB。

d2b5ca33bd970f64a6301fa75ae2eb22 1

开发使用建议

由于 Indexed DB 提供的 API 整体比较裸,在实际应用开发时,可能并不好用,你可以根据自己的需要,选择使用不同的第三方开发库来开发你的应用。在这篇文章中,我使用了 idb-keyval 来作为我的开发库。

d2b5ca33bd970f64a6301fa75ae2eb22 3

用法

首先,使用 yarn add idb-keyval 来安装依赖,安装完成后,可以参考如下代码来在你的项目中引入 indexedDB.

import { set,get,keys } from 'idb-keyval';


// 下面演示了一个 get_books 函数,会将内容存储在 IndexedDB 的 your-keys 当中。
// 如果存在缓存,则直接使用缓存,不存在,则进行数据获取
function get_books(){
   // 使用 keys 获取当前 IndexedDB 当中的所有 Key,用于判断当前是否有缓存结果。
   const exists_keys = await keys();
   if(exists_keys.indexOf('your-keys') !== -1){
    console.log("use cached glossary")
    return await get('your-keys');
   }

   // fetch data
   let data = fetch_data();
   
   await set('your-keys',data)
   return data;
}
Code language: PHP (php)

使用前后的效果

在性能上,使用 Indexed DB 之后,根据你的数据获取的难度,会有不同的性能提升。比如这里我不使用缓存,单次数据获取需要花费 800ms,借助于 Indexed DB,时间可以被控制在 10ms 以内,从而得到一个不错性能。

d2b5ca33bd970f64a6301fa75ae2eb22 2

从 LAMP 到 Docker based PaaS 工具

白宦成简史 当中,我写到过,我从 2013 年就开始写博客,至今已经 11 年有余。而我和互联网、编程的缘分,也从 2013 年开始。

在 2013 年的时候,我主要是使用 WordPress 建站(现在也还在用,比如本站)。所以,从哪个时候开始,我开始接触 LAMP、LNMP 这些个概念,并在过去的若干年里,使用了不少「一键安装包」来部署我的网站。

我用的一键安装包 / 控制面板不算少:LNMP.orgOneInStack(从它还是 LinuxEye 的时候开始用),LAMP.sh等一键配置包,AMHWDCPAppNodeWebminBTVestaCPVirtualmin等等一系列控制面板。

如果说这些工具有什么相同点,那便是都提供了十分方便的 LAMP / LNMP 的配置方式,让彼时不够专业的我、主要是用别人开发好的应用的我能够快速部署一个基于 MySQL + PHP 的应用,让它 Run 起来。

而随着时间的流逝,我已经不再是曾经的我了。我不再局限于使用别人写好的程序,我开始自己写;我不再局限于使用 PHP 来编写程序,我同样会使用 Python、Ruby、Golang 、Node.js 来编写应用程序;所有的这些,都告诉我,我需要在现有的框架和程序上去做很多额外的配置,比如,我需要在 LNMP 的基础之上,配置 NPM,以完成 Node.js 的构建;我需要在系统上配置 Docker ,以便于去运行某些需要复杂配置的环境。

曾经那些可以帮到我的程序已经不再能帮到我了,如今的他们,成了我的累赘。我开始需要为了他们去多做一些事情了。

如今的我,更需要的是一个能够基于 Docker 来运行的管理工具,能够帮助我完成不同环境的配置、管理的能力。我需要的是一个类似于 Heroku 的管理工具,能够让我把更多的精力放在把事做好上。

我试用了

最终,选择了 CapRover ,主要原因有几个:

  1. 支持基础的 Docker 管理功能:这样意味着我其实可以在网页端管理这些资源。
  2. 使用 Nginx ,并集成了 Let’s Encrypt:我的应用都希望有 HTTPs 的能力,所以默认集成了 Let’s Encrypt 可以帮助我解决不少的问题。我也不需要自己去维护一个 Traefik 来解决请求转发的问题(我没有使用 Rancher / Kubesphere 之类的容器管理平台也是这个原因)
  3. 提供了一些一键配置的 Sample:这意味着我把一些我常用的应用迁移过去的时候,可以抄袭一下其官方推荐的配置,可以降低我的使用门槛。
  4. 足够久远:CapRover 作为一个从 2017 年就开始运作的工具,代表着有足够多的 issue 可以供我参考 / 使用,可以减少我踩坑的概率。
  5. 提供了 CLI 来进行部署:对于一个经常需要部署的人来说,提供 CLI / Github Action 可以帮助我快速实现多种不同需求下的部署,帮助我来提升效率。

种种的这些,让我最终从过去的 LNMP,跳船到了 Docker Base PaaS 工具上。

Hexo 构建过程中报错 FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed – JavaScript heap out of memory 如何处理?

最近在处理 Linux 中国的静态站点,在技术选项上,为了方便修改,选择了 Hexo 来建设。

数据从 Discuz 转换到 Markdown 已经处理好了,但在构建过程中遇到了一些问题,会报如下错误

☁  linux [main] ⚡  hexo g
INFO  Validating config
INFO  Start processing
INFO  Files loaded in 2.37 min

<--- Last few GCs --->

[4685:0x118008000]   188193 ms: Scavenge (reduce) 3974.1 (4131.6) -> 3974.1 (4131.6) MB, 1.96 / 0.00 ms  (average mu = 0.143, current mu = 0.117) allocation failure;
[4685:0x118008000]   188198 ms: Scavenge (reduce) 3977.5 (4135.0) -> 3977.5 (4135.0) MB, 1.88 / 0.00 ms  (average mu = 0.143, current mu = 0.117) allocation failure;
[4685:0x118008000]   188202 ms: Scavenge (reduce) 3981.0 (4138.5) -> 3981.0 (4138.5) MB, 1.79 / 0.00 ms  (average mu = 0.143, current mu = 0.117) allocation failure;


<--- JS stacktrace --->

FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory
----- Native stack trace -----

 1: 0x104762660 node::OOMErrorHandler(char const*, v8::OOMDetails const&) [/opt/homebrew/Cellar/node/21.5.0/bin/node]
 2: 0x1048dcc84 v8::Utils::ReportOOMFailure(v8::internal::Isolate*, char const*, v8::OOMDetails const&) [/opt/homebrew/Cellar/node/21.5.0/bin/node]
 3: 0x1048dcc34 v8::internal::V8::FatalProcessOutOfMemory(v8::internal::Isolate*, char const*, v8::OOMDetails const&) [/opt/homebrew/Cellar/node/21.5.0/bin/node]
 4: 0x104a82410 v8::internal::Heap::CallGCPrologueCallbacks(v8::GCType, v8::GCCallbackFlags, v8::internal::GCTracer::Scope::ScopeId) [/opt/homebrew/Cellar/node/21.5.0/bin/node]
 5: 0x104a84e98 v8::internal::Heap::ComputeMutatorUtilization(char const*, double, double) [/opt/homebrew/Cellar/node/21.5.0/bin/node]
 6: 0x104a84b80 v8::internal::Heap::RecomputeLimits(v8::internal::GarbageCollector) [/opt/homebrew/Cellar/node/21.5.0/bin/node]
 7: 0x104a83f08 v8::internal::Heap::PerformGarbageCollection(v8::internal::GarbageCollector, v8::internal::GarbageCollectionReason, char const*) [/opt/homebrew/Cellar/node/21.5.0/bin/node]
 8: 0x104a829a4 v8::internal::Heap::CollectGarbage(v8::internal::AllocationSpace, v8::internal::GarbageCollectionReason, v8::GCCallbackFlags)::$_6::operator()() const [/opt/homebrew/Cellar/node/21.5.0/bin/node]
 9: 0x104a8277c void heap::base::Stack::SetMarkerAndCallbackImpl<v8::internal::Heap::CollectGarbage(v8::internal::AllocationSpace, v8::internal::GarbageCollectionReason, v8::GCCallbackFlags)::$_6>(heap::base::Stack*, void*, void const*) [/opt/homebrew/Cellar/node/21.5.0/bin/node]
10: 0x104680028 PushAllRegistersAndIterateStack [/opt/homebrew/Cellar/node/21.5.0/bin/node]
11: 0x104a8122c v8::internal::Heap::CollectGarbage(v8::internal::AllocationSpace, v8::internal::GarbageCollectionReason, v8::GCCallbackFlags) [/opt/homebrew/Cellar/node/21.5.0/bin/node]
12: 0x104a7977c v8::internal::HeapAllocator::AllocateRawWithLightRetrySlowPath(int, v8::internal::AllocationType, v8::internal::AllocationOrigin, v8::internal::AllocationAlignment) [/opt/homebrew/Cellar/node/21.5.0/bin/node]
13: 0x104a79f20 v8::internal::HeapAllocator::AllocateRawWithRetryOrFailSlowPath(int, v8::internal::AllocationType, v8::internal::AllocationOrigin, v8::internal::AllocationAlignment) [/opt/homebrew/Cellar/node/21.5.0/bin/node]
14: 0x104a61988 v8::internal::Factory::AllocateRaw(int, v8::internal::AllocationType, v8::internal::AllocationAlignment) [/opt/homebrew/Cellar/node/21.5.0/bin/node]
15: 0x104a58874 v8::internal::MaybeHandle<v8::internal::SeqTwoByteString> v8::internal::FactoryBase<v8::internal::Factory>::NewRawStringWithMap<v8::internal::SeqTwoByteString>(int, v8::internal::Tagged<v8::internal::Map>, v8::internal::AllocationType) [/opt/homebrew/Cellar/node/21.5.0/bin/node]
16: 0x104d74a6c v8::internal::Runtime_StringBuilderConcat(int, unsigned long*, v8::internal::Isolate*) [/opt/homebrew/Cellar/node/21.5.0/bin/node]
17: 0x104573954 Builtins_CEntry_Return1_ArgvOnStack_NoBuiltinExit [/opt/homebrew/Cellar/node/21.5.0/bin/node]
18: 0x1045e94c4 Builtins_RegExpReplace [/opt/homebrew/Cellar/node/21.5.0/bin/node]
19: 0x1045625ac Builtins_StringPrototypeReplace [/opt/homebrew/Cellar/node/21.5.0/bin/node]
20: 0x1044e8b84 Builtins_InterpreterEntryTrampoline [/opt/homebrew/Cellar/node/21.5.0/bin/node]
21: 0x1289db514
22: 0x1289dbae4
23: 0x128def07c
24: 0x1289db514
25: 0x128d2de58
26: 0x1289db514
27: 0x1289d9e20
28: 0x1288da1d8
29: 0x128de4df4
30: 0x1288ce2bc
31: 0x1288c99d8
32: 0x1288de57c
33: 0x1288de6d8
34: 0x1288d0654
35: 0x1044e68ac Builtins_JSEntryTrampoline [/opt/homebrew/Cellar/node/21.5.0/bin/node]
36: 0x1044e6594 Builtins_JSEntry [/opt/homebrew/Cellar/node/21.5.0/bin/node]
37: 0x1049fca88 v8::internal::(anonymous namespace)::Invoke(v8::internal::Isolate*, v8::internal::(anonymous namespace)::InvokeParams const&) [/opt/homebrew/Cellar/node/21.5.0/bin/node]
38: 0x1049fc480 v8::internal::Execution::Call(v8::internal::Isolate*, v8::internal::Handle<v8::internal::Object>, v8::internal::Handle<v8::internal::Object>, int, v8::internal::Handle<v8::internal::Object>*) [/opt/homebrew/Cellar/node/21.5.0/bin/node]
39: 0x1048f093c v8::Function::Call(v8::Local<v8::Context>, v8::Local<v8::Value>, int, v8::Local<v8::Value>*) [/opt/homebrew/Cellar/node/21.5.0/bin/node]
40: 0x104681110 node::InternalMakeCallback(node::Environment*, v8::Local<v8::Object>, v8::Local<v8::Object>, v8::Local<v8::Function>, int, v8::Local<v8::Value>*, node::async_context) [/opt/homebrew/Cellar/node/21.5.0/bin/node]
41: 0x104681508 node::MakeCallback(v8::Isolate*, v8::Local<v8::Object>, v8::Local<v8::Function>, int, v8::Local<v8::Value>*, node::async_context) [/opt/homebrew/Cellar/node/21.5.0/bin/node]
42: 0x1047001fc node::Environment::CheckImmediate(uv_check_s*) [/opt/homebrew/Cellar/node/21.5.0/bin/node]
43: 0x107d57ec8 uv__run_check [/opt/homebrew/Cellar/libuv/1.47.0/lib/libuv.1.dylib]
44: 0x107d52cc4 uv_run [/opt/homebrew/Cellar/libuv/1.47.0/lib/libuv.1.dylib]
45: 0x1046819dc node::SpinEventLoopInternal(node::Environment*) [/opt/homebrew/Cellar/node/21.5.0/bin/node]
46: 0x1047a9ad4 node::NodeMainInstance::Run(node::ExitCode*, node::Environment*) [/opt/homebrew/Cellar/node/21.5.0/bin/node]
47: 0x1047a983c node::NodeMainInstance::Run() [/opt/homebrew/Cellar/node/21.5.0/bin/node]
48: 0x104722e80 node::Start(int, char**) [/opt/homebrew/Cellar/node/21.5.0/bin/node]
49: 0x187fb10e0 start [/usr/lib/dyld]
[1]    4685 abort      hexo g
Code language: HTML, XML (xml)

这个报错中,最有价值的便是 FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed – JavaScript heap out of memory

这个报错的意思是目前 JavaScript 使用的内存已经超出了可用范围,导致程序挂掉,而如果想要解决这个问题,只需要分配更多的内存给 Hexo 即可。

只需要在构建的命令前加入NODE_OPTIONS=--max-old-space-size=24576,就可以分配更多的内存给 Node.js 。这里的 24576 是 24GB 内存的含义,你可以根据你的需要来选择。

PS. Linux 中国的数据太多,以至于我用 Hexo 分配了 24GB 都不行。。我决定换 Hugo 了。。希望 Hugo 可以。。。

如何批量取消你的 B 站关注和 Youtube 关注

You Need or You Want? 当中,我提到,我在清理我的关注,取消那些我很久不看的频道,简单分享一下如何做这个动作。

Youtube 关注

YouTube 可以访问 Channels 页面,然后手动取消关注( Youtube 的取关还有个二次确认,所以没办法像 B 站那样一条命令取消关注当前页面的 所有 Up 主)

B 站关注

B 站的取消关注动作相对简单很多。由于 B 站提供了按「最常观看」的排序的方式,所以我们只需要选择使用这个排序,并切换到列表最后一页,批量取关即可。

批量取关你可以使用下面这个命令,来取关整个页面上的所有 UP 主。

$(".be-dropdown-item:contains('取消关注')").click()
Code language: JavaScript (javascript)

具体的步骤如下:

一、在个人主页打开关注管理页面,并切换至全部关注的「最常访问」排序列表。

d2b5ca33bd970f64a6301fa75ae2eb22 4

二、使用 F12 或使用选项打开开发者工具,并切换到 Console 页面(中文是控制台)

d2b5ca33bd970f64a6301fa75ae2eb22 5

三、粘贴上面的代码,就可以取消关注当前页面的所有 UP 主了。

需要注意的是,每次执行会取关当前页面的,你需要切换一下底部的翻页器,切换到其他页再执行上面的命令。

此外,你还需要关注执行频率,如果执行频率太高,可能会弹出一个报错。这个时候只需要刷新一下即可。

d2b5ca33bd970f64a6301fa75ae2eb22 6

如何在 M1 mac 上安装 MySQL2 Gem

在 M1 的 mac 上安装 mysql2 这个 gem 的时候,经常会遇到如下的报错:

1 warning generated.
compiling statement.c
linking shared-object mysql2/mysql2.bundle
ld: warning: -multiply_defined is obsolete
ld: warning: ignoring duplicate libraries: '-lruby.3.2'
<strong>ld: library 'zstd' not found
</strong>clang: error: linker command failed with exit code 1 (use -v to see invocation)
make: *** [mysql2.bundle] Error 1
Code language: PHP (php)

根据提示,我们可以看到是 zstd 这个依赖找不到导致的编译失败。这是因为 mysql2 是一个 Native Gem,依赖了大量的系统组件,如果我们没有对应的系统组件,就无法找到。

但实际上在我的系统中已经安装了 zstd,只是在 mysql2 的构建过程中找不到。

d2b5ca33bd970f64a6301fa75ae2eb22 3

要解决这个问题有几个思路:

  1. 在构建时能找到 zstd ;
  2. 在构建时指定 zstd 的位置。

这里我选择第二种方式:

gem install mysql2 -- --with-mysql-config=$(brew --prefix mysql)/bin/mysql_config --with-ldflags="-L$(brew --prefix zstd)/lib -L$(brew --prefix openssl)/lib" --with-cppflags=-I$(brew --prefix openssl)/include
Code language: JavaScript (javascript)

通过 -- -with-mysql-config 的方式,可以在安装时指定构建的参数,从而实现让 gem 构建时使用我们设置的路径,从而完成 gem 的安装。

d2b5ca33bd970f64a6301fa75ae2eb22 4

如何自定义 Docked?

我在之前的文章 使用 Docked Rails CLI 简化 Rails 的开发 中介绍了 Ruby on Rails 的 Docked 程序,并提供了一个我自己的定制版本。

这里来和大家说一下怎么自定义 Docked 镜像,从而构建一个适合你自己的镜像。

Fork Docked 项目

At first ,你需要 Fork Rails 官方的项目

https://github.com/rails/docked

Fork 项目到你自己的名下后,你可以修改一下他的名字,改成适合你自己习惯的名字(比如我就改成 Runs 了,Docked 对我来说太容易打成 Docker 了)。

修改 Dockerfile

Docked 最核心的其实就是 Dockerfile ,你可以修改你 Fork 来的项目,并在 Dockerfile 当中添加必要的依赖,引入新的资料等。

比如,https://github.com/bestony/runs/commit/d930a5d6fc389cb6fa8e9f7c41947d01b000da95这个 Commit 就是为了在 Dockerfile 当中添加 PGSQL 的配置,以实现在使用 rails new 命令时,可以选用 PGSQL as DB Backend。

修改 Ruby 版本

可参考:https://github.com/bestony/runs/commit/31fabe5f914d931834b0e12797b14d76bf56d162

修改 Node 版本

可参考:https://github.com/bestony/runs/commit/5969cc4ee5c0bf8503ebdab5664f365b6719843e

修改编译脚本,上传镜像

修改完成 Dockerfile 后,接下来你需要修改 Docker镜像产物,以便于你自己在实际使用过程中,直接使用你自己的 Docker 镜像。

修改 https://github.com/bestony/runs/blob/160fe165db7abecc3229be417b15473dcd3aec9f/.github/workflows/docker-publish.yml#L41 的 tags 为你自己的,格式为 ghcr.io/{你的 ID}/{你的仓库名}

修改好之后,只需要提交 Commit ,等待 Github Action 的自动构建即可。

修改 Readme

镜像构建结束,你只需要修改 Readme 中的安装配置命令,这样在后续使用时就不用自己再修改了。重点修改的内容包括 ailas、镜像名以及底部的启动命令。

d2b5ca33bd970f64a6301fa75ae2eb22

总结

通过对 Docked 的简单修改,可以实现快速构建一个属于你自己的开发环境命令,帮你优化自己的工作流。

使用 Docked Rails CLI 简化 Rails 的开发

在开发 RoR 的时候,经常需要配置本地的开发环境。但如果你需要在一些云端开发环境(比如 Github Codespaces)中配置你的开发环境时,就会变得比较麻烦。

但得益于 Docker,我们可以直接使用 Docker 镜像来完成我们的开发环境。

Ruby 官方提供了 Docked 来帮助我们完成这个环境的构建。

配置

假设你已经完成了 Docker 的安装,接下来你只需要做如下操作,来配置 Docked Rails Cli

docker volume create ruby-bundle-cache
alias docked='docker run --rm -it -v ${PWD}:/rails -v ruby-bundle-cache:/bundle -p 3000:3000 ghcr.io/rails/cli'
Code language: JavaScript (javascript)

为了方便你的使用,你还可以将上述的输入放在 .bash_rc.bash_profile 当中。

使用

接下来,你只需要使用 docked 你要执行的命令 来执行各种命令,比如官方给出的这样的 Sample。

docked rails new weblog
cd weblog
docked rails generate scaffold post title:string body:text
docked rails db:migrate
docked rails server
Code language: CSS (css)

updates in 2023.12.19

由于官方默认的 docked 没有 PGSQL 的支持,所以我自己 Fork 了一个版本,做了一些更新。

具体可以见 https://github.com/bestony/runs

FastAPI 在使用 pytest 加载不同的配置文件,以实现测试环境单独运行某些配置项目

引言

Fast API 作为一个新兴的 Python Web 框架,不少的开发者都在使用 FastAPI 来开发应用。我最近也在试着使用它。

在开发应用时,我习惯使用 TDD 的方式进行开发,特别是开发飞书机器人时,由于飞书给我提供的请求是可预测的,特别适合以 TDD 的方式来定义行为并实现。

同时,我在使用其他语言开发的时候,也会用到使用 .env.env.testing 这样的配置文件来在不同的环境下加载不同的配置文件,达到在不同环境使用不同的变量来完成任务。

内容大纲

  1. 实现读取 .env 文件
  2. 实现在测试环境读取 .env.testing 文件

模块详细内容

实现读取 .env 文件

想要实现在 FastAPI 中读取 .env 文件,首先,你需要引入 pydantic_settings 包,来完成基本的配置文件的读取。

from pydantic_settings import BaseSettings

class Settings(BaseSettings):
    app_name: str = "Awesome API"

settings = Settings()
print(settings.app_name)

接下来你可以为这个 Settings 类新增 Config 配置,以实现读取本地的 .env 文件

from pydantic_settings import BaseSettings

class Settings(BaseSettings):
    app_name: str = "Awesome API"

    class Config:
        env_file = ".env"

settings = Settings()
print(settings.app_name)

通过上述代码,你的配置项目就会自动读取 .env 文件来加载环境变量,从而实现更加简单的环境变量管理。

实现在测试环境读取 .env.testing 文件

在测试时,为了避免使用线上的数据,你可以在测试时加载不同的环境变量文件。

如果你只有一个测试环境,可以有一个非常简单的方式来实现:在配置 env file 时,传入一个元组,会自动加载多个文件。

from pydantic_settings import BaseSettings

class Settings(BaseSettings):
    app_name: str = "Awesome API"

    class Config:
        env_file = (".env",".env.testing")

settings = Settings()
print(settings.app_name)

需要注意,这里实现的实际上是使用 pydantic-settings 自己的加载顺序来实现。默认情况下,靠后的文件优先级会更高(覆盖了前面的内容)。这样的好处是如果你有些配置测试环生产是一样的,可以在 .env.testing 中加入不同的部分,相同的部分直接复用生产环境的配置项目即可。

但由于使用的是顺序加载多个问题,如果你发现表现和你配置的不一致时,就要看看是不是有优先级更高的环境变量文件覆盖了默认的 .env 文件。

结论

借助 pydantic-settings 的一些配置,你可以快速实现 .env.env.testing 的加载,相比与判断环境变量,这样会比较简单,且可以实现在 .env.testing 中只维护变量,和线上保持一致的部分可以直接继承。


附加部分