#Porting Python App to Typescript

1 messages · Page 1 of 1 (latest)

steady mortar
#

Hi, I am currently learning Typescript, for me the best way seems to be porting an already known app written in python to Typescript.
But as always while learning new languages there are some specific implementation details.
Given following class which is some kind of proxy vor environment variables. What it basically does is defering the resoltion of the given envrionment variable until the value is requested.

class EnvProxy:
    def __init__(self, key: str) -> None:
        self._key = key

    def __get__(self, obj: Any, objtype: Any = None) -> str:
        if os.getenv("CI"):  # when running within a gitlab ci pipeline
            return os.environ[self._key]

        # indicate that we are not running within a pipeline by
        # returning an empty string
        if self._key == "CI":
            return ""

        # in the case we are not running within a pipeline ($CI is empty)
        # for all other variables we return a dummy value which
        # explicitly describe this state
        return os.getenv(self._key, "notRunningInAPipeline")

To implement the functionality into other classes I am using this EnvProxy class and assign an instance to a variable.

class PredefinedVariables:
    CI: EnvProxy = EnvProxy("CI")
    """
    Mark that job is executed in CI environment.

    Added in GitLab all
    Available in GitLab Runner 0.4

    Raises:
        KeyError: If environment variable not available.
    """

As soon as I am using this CI variable inside my app the key the __get__() method is invoked and returns the environment variable value.

if PredefinedVariables.CI:
  print("Running in CI")

How can I achieve the same behavior in TS?

Regards!

strong sigil
#

I don't think this would be a common pattern for something like environment variables in JS/TS though.

#

Proxy is advanced and complicated from a type perspective - having a function that returns a given environment variable would be a lot more common.

steady mortar
#

The challenge is, that I have to defer the process.env call until it is actually requested.

#

Maybe the docstring of the EnvProxy does describe the purpose a little bit better.

class EnvProxy:
"""This proxy delays the operating system query for environment variables until the value is requested.

    This proxy is designed for requesting predefined environment variables that must
    exist within a Gitlab CI pipeline execution. Create an object from this proxy
    with the name of the Gitlab `CI_*` variable you want to query:

 
    CI_COMMIT_REF_SLUG = EnvProxy("CI_COMMIT_REF_SLUG")  # os.environ is not (!) called here
    ...
   

    When using an object of this proxy in a statement it returns the value of the `CI_*`
    variable requested or raises a `KeyError` if against our expectations the variable
    is not available.

    
    ...
    if CI_COMMIT_REF_SLUG == "foobar":  # <- os.environ is called here
        ...
  
    The proxy has a different behavior when not being called within a Gitlab CI pipeline
    execution. This is, when the environment variable `CI` is unset (from the official
    Gitlab CI docs). Then the proxy does not raise a KeyError for `CI_*` variables, because
    they naturally does not exist (or at least their existence is not guaranteed).
    So if we are not running within a Gitlab CI pipeline, the proxy insteads returns the
    dummy string `notRunningInAPipeline` for all `CI_*` variables. Except for the `CI`
    variable itself, where an empty string is returned, indicating we are not running
    within a pipeline

    Args:
        key (str): The name of the environment variable that should be queried on request.
    """
odd rune
#

Perhaps I'm missing the point of how it works in Python, I don't see how it's different from just having a function getEnv, which internally resolves environment variable differently depending on some conditions, and returns the value?

#

Ah in JS you can't proxy a naked variable (at least not without some hacky magic that you really don't want to use), you would just do something like getEnv('CLI_COMMIT_REF_SLUG'), or if you really want to proxy stuffs for some reason env.CLI_COMMIT_REF_SLUG (which env would be a proxy object)

steady mortar
#

Mhh. Ok. The exact implementation doesn't matter. For the user it should feel like a simple constant with the same name as the requested/available without any parentheses. E.g. PredefinedVariables.CI_COMMIT_REF_SLUG I have no specific idea how to implement this.

odd rune
#

Do you only have a set of known variables? Or do you want the ability to access any variable?

#

For the former a good old object with getters would be my way to go, for the latter a function or proxy seems right.

steady mortar
#

Actually there are a lot variables but I will implement each by myself, so it is a set of known variables.

odd rune
#

Yeah an object with getters then, something like:

const env = {
    get commitRefSlug() {
        // implementation
    }
}

Consumer:

console.log(env.commitRefSlug)
steady mortar
#

Thank you. I will try that an will come back with my outcome.
Just cause I am curious. What actually does the Proxy?

odd rune
#

Normally if you have an object:

const foo = {
    aPropertyKey: 42
}

And you do foo.aPropertyKey it will just look it up and return 42

#

With Proxy you can change the lookup behavior (and other behaviors) to do whatever you want.

strong sigil
#

Here's the sort of setup I usually recommend for env variables, fwiw:

#

!retsam19:env-config

ornate pumiceBOT
#
retsam19#0
`!retsam19:env-config`:

This is the sort of pattern I recommend for consuming environment variables (and potentially other config) in TS:

// config.ts
export function expectEnv(key: string) {
    const val = process.env[key];
    if(!val) throw new Error(`Expected ${key} environment variable to be defined`);
    return val;
}

export const FOO = expectEnv("FOO");
export const BAR = expectEnv("BAR");
export const BAZ = process.env.BAZ ?? "defaultValue"