Repository: open-telemetry/opentelemetry-python

Issue:
Metric units should be treated as opaque strings
**Describe your environment**
OpenTelemetry Python API and SDK versions 1.12.0rc1 running under Python 3.10.

**Steps to reproduce**
Create an instrument with a unit containing characters that are not alphanumeric.

```py
from opentelemetry import metrics

meter = metrics.get_meter("server_meter")

meter.create_counter(
    name="request_count",
    description="Count the number of handled requests.",
    unit="{requests}",
)
```

**What is the expected behavior?**
The unit validation should only check that the unit has fewer than 63 ASCII characters.

**What is the actual behavior?**
Units are restricted to the following regex character set: `[a-zA-Z0-9_]`.

An exception is thrown with the following message:
```
Expected ASCII string of maximum length 63 characters but got {requests}
```

**Additional context**
The unit validation regex being used: https://github.com/open-telemetry/opentelemetry-python/blob/main/opentelemetry-api/src/opentelemetry/metrics/_internal/instrument.py#L43

The API spec advises that units should be treated as opaque strings and only the length should be validated: https://opentelemetry.io/docs/reference/specification/metrics/api/#instrument-unit

Python defines `\w` as `[a-zA-Z0-9_]` when the ASCII flag is used: https://docs.python.org/3/library/re.html#regular-expression-syntax



Discussion:
@evan-bradley thanks for clear bug report. Any change you'd be willing to send a PR?
I'll work on this one :+1: 

Resolving patch:
diff --git a/opentelemetry-api/src/opentelemetry/metrics/_internal/instrument.py b/opentelemetry-api/src/opentelemetry/metrics/_internal/instrument.py
--- a/opentelemetry-api/src/opentelemetry/metrics/_internal/instrument.py
+++ b/opentelemetry-api/src/opentelemetry/metrics/_internal/instrument.py
@@ -18,7 +18,6 @@
 from abc import ABC, abstractmethod
 from dataclasses import dataclass
 from logging import getLogger
-from re import ASCII
 from re import compile as re_compile
 from typing import (
     Callable,
@@ -39,8 +38,8 @@
 
 _logger = getLogger(__name__)
 
-_name_regex = re_compile(r"[a-zA-Z][-.\w]{0,62}", ASCII)
-_unit_regex = re_compile(r"\w{0,63}", ASCII)
+_name_regex = re_compile(r"[a-zA-Z][-_.a-zA-Z0-9]{0,62}")
+_unit_regex = re_compile(r"[\x00-\x7F]{0,63}")
 
 
 @dataclass(frozen=True)


Test patch:
diff --git a/opentelemetry-api/tests/metrics/test_instruments.py b/opentelemetry-api/tests/metrics/test_instruments.py
--- a/opentelemetry-api/tests/metrics/test_instruments.py
+++ b/opentelemetry-api/tests/metrics/test_instruments.py
@@ -571,6 +571,7 @@ def test_name_regex(self):
         self.assertTrue(instrument._check_name_and_unit("a.", "unit")[0])
         self.assertTrue(instrument._check_name_and_unit("a-", "unit")[0])
         self.assertTrue(instrument._check_name_and_unit("a_", "unit")[0])
+
         self.assertFalse(instrument._check_name_and_unit("a" * 64, "unit")[0])
         self.assertFalse(instrument._check_name_and_unit("Ñ", "unit")[0])
         self.assertFalse(instrument._check_name_and_unit("_a", "unit")[0])
@@ -582,5 +583,7 @@ def test_unit_regex(self):
         instrument = ChildInstrument("name")
 
         self.assertTrue(instrument._check_name_and_unit("name", "a" * 63)[1])
+        self.assertTrue(instrument._check_name_and_unit("name", "{a}")[1])
+
         self.assertFalse(instrument._check_name_and_unit("name", "a" * 64)[1])
         self.assertFalse(instrument._check_name_and_unit("name", "Ñ")[1])